Overview

最近我跟Young在开发secretepdb这个项目时,遇到了很多跟Struts2Hibernate相关的问题,在这里记录Struts2的相关信息和问题。其实YoungPhosphoPrediction项目总结 已经总结了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包,也导致Struts2Struts1几乎是完全不同的东西。

除了上面的两个核心包,还需要添加这两个包的依赖包:

  • 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.0jar(commons-lang-2.4.jar)包不兼容了,那这两个包就不能相互替换了,可能不同的包会依赖不同版本的lang包,那就需要导入不同版本的lang包,因为lang 3.0包已经改了包名,所以同时引入commons-lang3-3.2.jarcommons-lang-2.4.jar也就不会出现包冲突了。 这也是为什么struts-2.3.24.1的完整包的lib文件夹中,同时给出了commons-lang3-3.2.jarcommons-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项目都会有的基础配置文件,下面是一个配置了Struts2web.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">这条配置为例,它定义了一个叫searchDBByIDaction配置,并关联到action包下的SearchDBByIDAction类的execute方法。

1.4 前端访问Struts2action以及向后台传值

在前端页面下使用下面的方式访问这条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属性的值一样,并设置好getset方法,就会自动接收到前端的值。同理,proteinlist变量也设置好getset方法,就可以用来存储后台的结果供前端页面读取,下面再说前端页面如何读取。

其实可以看得出来,不管从前端向后传值,还是后端向页面传值,本质上都是一样的,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变量,但是忘记设置getset方法。

2.3 Struts2访问static文件夹下的静态资源

这是个比较奇葩的问题。通常我们会把jscss,图片等静态资源放到一个static文件夹中,下图是一个典型的Struts2项目目录:

project_structure.jpeg

在页面中,引入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文件夹中移出来,让cssstatic平级,然后在页面中引入:

<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/.*?" />