Overview
最近我跟Young
在开发secretepdb
这个项目时,遇到了很多跟Struts2
和Hibernate
相关的问题,在这里记录Struts2
的相关信息和问题。其实Young
在 PhosphoPrediction项目总结 已经总结了Struts2
的很多东西,再写一篇是想把所有相关的写在一起,方便参考。
1. Struts2
的配置
先去 Struts2官网 上下载一个完整的包,就包含了所有你想要的东西。下面还会提到,直接使用完整包里提供的lib会避免包不兼容的问题,因为官网上一起给出的包肯定都是兼容的。
目前,官网首页上的Struts2
就是struts-2.3.24.1
,下载这个版本的完整包struts-2.3.24.1-all.zip
。
解压之后里面有四个文件夹:
apps
中存放的是Struts2
相关的可运行war包,想了解的直接把这里面的包扔到tomcat
上,就可以通过浏览器访问。docs
中包含了所有的文档,包括Struts2
的说明文档,API文章,以及Struts2
集成的插件的API文章等。lib
中包含了Struts2
所需要的jar包,以及使用Struts2
集成插件所需要的包。src
中就是Struts2
的源代码,通常来说,随着对一个框架的不断熟悉,文档已经不能满足你的好奇心了,代码面前,了无秘密,阅读源码可以让你对所有你不能理解或者很奇葩的bug有更好的认识。下面还会提到一个这样的例子。
1.1 Struts2
需要的jar包
搭建Struts2
需要下面的核心包:
struts2-core-2.3.24.1.jar
这个不用解释了,名字就看得出来。xwork-core-2.3.24.1.jar
不了解Struts2
的历史,会对这个包有点疑惑。在Struts1
的时候,xwork
就是一个能够与之分庭抗礼的框架,实际上,xwork
框架做得更好,起码从技术上来说更好,不如Struts1
流行也只是因为Struts1
更先进入市场,抢占了先机。因此Struts2
中放弃了原来的Struts1
很多设计,合并了xwork
,这就是为什么Struts2
核心包中会出现xwork
包,也导致Struts2
跟Struts1
几乎是完全不同的东西。
除了上面的两个核心包,还需要添加这两个包的依赖包:
- commons-fileupload-1.3.1.jar
- commons-io-2.2.jar
commons-lang3-3.2.jar
- commons-logging-1.1.3.jar
- freemarker-2.3.22.jar
- javassist-3.11.0.GA.jar
- ognl-3.0.6.jar
不用太关注上面这组依赖包的版本号,一般来说,新版本的jar包总是向前包容的,所以更新的版本通常都能起到跟老版本一样的作用。不过,只有一个包例外,那就是commons-lang3-3.2.jar
。
Apache Commons 团队刚刚发布了 Commons Lang 3.0 ,该版本完全支持 Java 5 的特性,例如泛型和可变参数,删除了废弃的 API 。因此该版本无法兼容以前的版本,包名也做了更改 org.apache.commons.lang3 。
如果你想使用该版本,请先阅读移植指南:
http://commons.apache.org/lang/upgradeto3_0.html
这个新发布的lang 3.0
包,就是commons-lang3-3.2.jar
。既然已经跟lang2.0
jar(commons-lang-2.4.jar
)包不兼容了,那这两个包就不能相互替换了,可能不同的包会依赖不同版本的lang
包,那就需要导入不同版本的lang
包,因为lang 3.0
包已经改了包名,所以同时引入commons-lang3-3.2.jar
和commons-lang-2.4.jar
也就不会出现包冲突了。 这也是为什么struts-2.3.24.1
的完整包的lib文件夹中,同时给出了commons-lang3-3.2.jar
和commons-lang-2.4.jar
。需要哪个就导入哪个,struts-2.3.24.1
依赖的是commons-lang3-3.2.jar
,因此需要导入的是commons-lang3-3.2.jar
。
1.2 配置 web.xml
web.xml
位于项目根目录/WebContent/WEB-INF
目录下,它并不是Struts2
的配置文件,而是每个Web项目都会有的基础配置文件,下面是一个配置了Struts2
的web.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
<welcome-file-list>
标签是通用的Web配置,用来配置访问这个项目网站时默认的首页。
<filter>
标签定义了一个叫做struts2
的过滤器,被这个过滤器过滤的请求都会经过org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
类处理。
<filter-mapping>
设置了哪些url请求会被struts2
过滤处理,<url-pattern>
设置为/*
表示所有的网页请求都需要先经过struts2
处理。如果有些请求不想被struts2
处理,就需要在struts.xml
中配置,下面会说到这一点。
1.3 配置 struts.xml
一个典型的struts.xml
配置如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
<constant name="struts.action.excludePattern" value="/static/.*?" />
<package name="front" extends="struts-default" >
<action name="searchDBByID" class="action.SearchDBByIDAction" method="execute">
<result>
/searchResults.jsp
</result>
<result name="noResults">
/searchNoResults.html
</result>
</action>
</package>
</struts>
结合一个例子,说明这个配置文件。
<constant name="struts.devMode" value="true" />
,用于将启用开发模式,在开发过程中会在控制台输出很多错误或者异常信息供开发者参考。
<constant name="struts.action.excludePattern" value="/static/.*?" />
,过滤掉/static/
开头请求,当请求/static/
目录下的资源时,不经过Struts2
处理。下面还会提到一个不设置这项引发的错误。
<package>
中的每个<action>
就是前端页面和后台action
类的的桥梁,以<action name="searchDBByID" class="action.SearchDBByIDAction" method="execute">
这条配置为例,它定义了一个叫searchDBByID
的action
配置,并关联到action
包下的SearchDBByIDAction
类的execute
方法。
1.4 前端访问Struts2
的action
以及向后台传值
在前端页面下使用下面的方式访问这条action
配置:
<form action="searchDBByID">
<input type="text" id="ID" name="ID">
<button type="submit">Submit</button>
</form>
这里form
中的action
属性设置为searchDBByID
,也就是<action name="searchDBByID">
这里的值,就可以将这个请求发送到action
包下的SearchDBByIDAction
类,并执行SearchDBByIDAction
类中的execute
方法。
注意这里<input>
标签中的name
属性,后台需要通过这个属性取到<input>
中的值。
1.5 后台的Action处理类
public class SearchDBByIDAction extends ActionSupport {
private String ID;
//Used to store the results from DB and be fetched by JSP.
private List<Protein> proteinlist = new ArrayList<Protein>();
public String getID() {
return ID;
}
public void setID(String iD) {
ID = iD;
}
public List<Protein> getProteinlist() {
return proteinlist;
}
public void setProteinlist(List<Protein> proteinlist) {
this.proteinlist = proteinlist;
}
//Begin Search Action Method
public String execute() {
...
if(proteinlist==null | proteinlist.size()==0)
return "noResults";
return SUCCESS;
}
}
通过struts.xml的配置,前端页面的请求就可以找到这个Action
类,类中的ID
变量与<input type="text" id="ID" name="ID">
中name
属性的值一样,并设置好get
和set
方法,就会自动接收到前端的值。同理,proteinlist
变量也设置好get
和set
方法,就可以用来存储后台的结果供前端页面读取,下面再说前端页面如何读取。
其实可以看得出来,不管从前端向后传值,还是后端向页面传值,本质上都是一样的,Struts2
都会把这个值放在一个值栈(Value Stack
)中,然后在需要的时候去取。
当前台向后台传数据时,数据会被Struts2
接收到,放在值栈中,并在初始化action
类时,直接用值栈中的值初始化此action
中对应的接收变量;当后台向前台传数据时,Struts2
会把这个值放在值栈中供前台使用,只不过需要前台自己显示去取而已。
1.6 前端接收后台的传值
既然Struts2
已经将给前端的值放在了值栈中,那么前端所需要做的仅仅是把它取出来。下面是一个取上面的action
类中proteinlist
变量并展示到前端的例子:
<c:forEach items="${proteinlist }" var="l">
<tr >
<td ><a href = "showProteinDetail.action?proteinID=${l.getProteinID()}">${l.getProteinID() }</a></td>
<td ><a href = "http://www.uniprot.org/uniprot/${l.getUniprotID() }" target="_blank">${l.getUniprotID() }</a></td>
<td >${l.getName() }</td>
</tr>
</c:forEach>
可以看出,使用${变量名}
就可以直接取出来结果,只不过这里的proteinlist
是一个ArrayList,所以用了循环。如果对<c:forEach>
这样的标签感到困惑,可以去看一下 JSTL教程 。
2. Struts2
开发中常见的错误
2.1 HTTP Status 404
这个错误应该是最常见的错误了,一般会在你做了修改之后重新在tomcat
上部署之后,出现在浏览器页面中,具体的错误信息如下:
HTTP Status 404 - /SearchDB_0040/
type Status report
message /SearchDB_0040/
description The requested resource is not available.
Apache Tomcat/7.0.57
只要tomcat
在重新部署时遇到任何问题,都会拒绝将项目部署到tomcat
上,这时候原来的项目已经删了,而新项目没有部署上去,访问时自然是找不到这个项目。目前我们遇到的会引起这个错误的主要有以下几个原因。
2.1.1 包冲突
tomcat
在部署一个项目的时候,也会扫描一遍这个项目的lib
库,如果出现包冲突,就会在控制台输出信息,并拒绝将项目部署到tomcat上
。
2.1.2 struts.xml
配置问题
刚开始的时候出现HTTP Status 404错误,你可能很难去联系到struts.xml
,以下面这个配置为例:
<action name="submission" class="com.SubmissionAction" method="execute">
<result>
/submissionResult.html
</result>
</action>
tomcat
部署项目时,会扫描一遍struts.xml
,并为每条action
记录检查是否在对应的action
类文件,在上面这个例子中,tomcat就会去com
包下查看是否有SubmissionAction.java
,如果没有,tomcat
就会输出错误信息,拒绝将项目部署到tomcat
上。
好的编程习惯是,自底而上地去编程,写好了Action
类之后,再去配置struts.xml
中的action
信息。
2.2 传值错误
错误信息如下,但是可能又不仅限于这样的错误信息:
ERROR com.opensymphony.xwork2.interceptor.ParametersInterceptor:34 - Developer Notification (set struts.devMode to false to disable this message):
Unexpected Exception caught setting 'position' on 'class action.JsonAction: Error setting expression 'position' with value ['student', ]
这个错误的原因在于前端向Action
类传递了position
属性,而Action
类中没有相应的变量去接收,比如你在Action
类中定义的接受变量叫Position
,大小写没有完全匹配。或者你在Action
类中定义了position
变量,但是忘记设置get
和set
方法。
2.3 Struts2
访问static
文件夹下的静态资源
这是个比较奇葩的问题。通常我们会把js
,css
,图片等静态资源放到一个static
文件夹中,下图是一个典型的Struts2
项目目录:
在页面中,引入bootstrap-3.3.1.min.css
需要使用:
<link rel="stylesheet" type="text/css" href="static/css/bootstrap-3.3.1.min.css"/>
但是直接运行你会发现无法加载这个css
,使用Chrome
开发者工具会说资源找不到。但是如果你把css
文件夹从static
文件夹中移出来,让css
跟static
平级,然后在页面中引入:
<link rel="stylesheet" type="text/css" href="css/bootstrap-3.3.1.min.css" />
页面就可以找到这个静态资源。
查了一下资料,发现默认配置的Struts2
会过滤所有请求(上面有写),而过滤器遇到带有static/aaa/bbb
样子的请求时,会自动去掉static
,实际请求就变成了aaa/bbb
,所以自然也就找不到static
文件夹下的资源文件了。
解决方案很简单,配置struts.xml
,不再过滤static/
开头的请求就可以了。其实前面讲的struts.xml
已经写进去了,就是下面这句:
<constant name="struts.action.excludePattern" value="/static/.*?" />
写得很好,boy。我好好看看,哈~