Struts2

一、Struts的工作原理

Struts2的工作机制3.1Struts2体系结构图

  Strut2的体系结构如图15所示:

  (图15)

  3.2Struts2的工作机制

  从图15可以看出,一个请求在Struts2框架中的处理大概分为以下几个步骤:

  1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;

  2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);

  3、接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action;

  4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;

  5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;

  6、ActionProxy创建一个ActionInvocation的实例。

  7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。

8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper。

二.Action封装请求参数

1 Action如何获取请求参数

我们已经学习过使用ActionContext#getParameters()方法获取请求参数!但这种方式需要自己来获取,很麻烦。

我们还学习过在Actoin获取Servlet API,所以我们可以先获取request对象,然后再通过request对象来获取请求参数,例如:ServletActionContext.getRequest().getParameter(“xxx”)。

现在我们要学习新的方式,使用属性驱动,或模型驱动。

如果现在有一个login.jsp页面,它提交到LoginAction!那么在LoginAction中给出username和password属性,那么这就是属性驱动;如果在LoginAction中给出User类型的属性,那么就是模型驱动了。

public class LoginAction extends   ActionSupport {//属性驱动

private   String username;

private   String password;

….

}

public class LoginAction extends   ActionSupport {//模型驱动

private User   user;

….

}

2 属性驱动

属性驱动方式来封装请求参数,这需要在Action中给出属性,用来封装表单属性。并且属性名称需要与表单项名称相同!其实这是JavaBean规范,只要Action有setXXX()方法即可

回忆Struts2流程流,在Action执行之前会先去执行一系列的拦截器,拦截器与JavaWeb中的过滤器及其相似!你现在甚至可以把拦截器理解成过滤器。在系列拦截器中有一个命名为params的拦截器会负责把请求参数封装到Action的属性中。

我们知道,拦截器是在Action之前执行的,也就是说,在执行UserAction的login()方法之前,Struts2已经把表单数据封装到UserAction的username和password属性中了。所以在login()方法中可以大胆的使用这些属性。

并且不会出现UserAction有属性会出现多线程争用数据的问题!因为在Struts2中Action都不是单例的!struts2 Action 是多实例 ,为了在Action封装数据  (struts1 Action 是单例的 )

这种方式我们只需要做:让Action的属性名与表单项名称对应,那么拦截器会自动把请求参数装载到Action属性中。

3 模型驱动之OGNL

这里所说的模型驱动是在Action中给出JavaBean属性,而不是分散的username和password,而是使用User类型做为属性。

public class User {

private String username;

private String password;

……

}

public class UserAction extends ActionSupport {

private User user;

public void setUser(User user) {

this.user = user;

}

public User getUser() {

return user;

}

public String login() {

System.out.println(user);

return NONE;

}

}

  这种方式会使Action属性名称不再与表单项名称相同,但我们发现与表单项名称相同的是Action的User属性的属性。我们如果可以让拦截器知道,去Action的User属性中去寻找属性。这需要使用OGNL表达式:

<form action="<c:url value='/loginAction.action'/>" method="post">

用户名:<input type="text" name="user.username"/><br/>

密 码:<input type="password" name="user.password"/><br/>

<input type="submit"   value="登录"/>

</form>

  在表单中我们给出的表单项名称是user.username和user.password,这就是OGNL表达式,它表达的意思是,去找Action的名字为user的属性,再到user中去找username属性,这才是装载表单参数的地方。

上面例子中需要注意:

l  创建User类,User类需要提供setUsername()和setPassword()方法,与表单对应;

l  UserAction中创建User类型的属性,属性名必须为user,并提供setUser()和getUser()方法;

l  页面中表单项的名称必须使用OGNL表达式,例如user.username表示把该项值赋给UserAction的user属性的username属性。Struts2在首次赋值时会先创建User对象,然后再给User的username属性赋值,最后调用setUser()方法把User对象传递给UserAction;

l  当再次通过OGNL表达user.password赋值时,这不是第一次赋值,所以Struts2会先去调用UserAction的getUser()方法获取User对象(因为第一次已经创建了,从此就不在创建),这时如果UserAction没有提供getUser()方法,就会出现第二次赋值失败的情况。如果存在getUser()方法,那么在获取到User对象后,再通过setPassword()给User对象指定属性值。

4 模型驱动之ModelDriven接口

这种方式与OGNL方式很相似,但表单页面中还是使用属性驱动的方式,不使用OGNL表达式,表单如下:

<form action="<c:url value='/loginAction.action'/>" method="post">

用户名:<input type="text" name="username"/><br/>

密 码:<input type="password" name="password"/><br/>

<input type="submit"   value="登录"/>

</form>

这时,Struts2还会去调用UserAction的setUsername()和setPassword()方法,但我们知道UserAction没有这两个方法,因为UserAction的属性是user。我们必须让Sturts2知道,我们使用的是模型驱动,这时就要求UserAction实现ModelDriven接口。

public interface ModelDriven<T> {

T getModel();

}

ModelDriven接口只有一个方法:getModel(),它用来返回model,在本例中应该返回的就是User对象。

public class UserAction extends ActionSupport implements   ModelDriven<User> {

private User user = new User();

@Override

public User getModel() {

return user;

}

public String login() {

System.out.println(user);

return NONE;

}

}

这种方式是真正的模型驱动,使用模型驱动方式完成参数封装是由modelDriven拦截器来完成的。它会检查UserAction是否实现了ModelDriven接口,如果实现了,那么就调用它的getModel()方法得到User对象,然后再去调用User对象的setUsername()和setPassword()来封装表单数据。注意,返回的user对象必须手动初始化,如果getModel()方法返回的是null,那么就无法封装表单数据。

如果UserAction没有实现ModelDriven接口,那么Struts还会去查询UserAction是否存在setUsername()和setPassword()方法,即以属性驱动方式来封装表单数据,但因为UserAction没有这两个方法,那么就会失败。

5 集合参数(了解)

其实这种方式还是OGNL方式!

当UserAction的属性为List<User>类型时,即不是一个User,而是一个集合时,那么在表单中需要使用“特殊”的OGNL表达式。

public class UserAction extends ActionSupport {

private List<User> userList;

public List<User>   getUserList() {

return userList;

}

public void setUserList(List<User>   userList) {

this.userList = userList;

}

public String login() {

System.out.println(userList);

return NONE;

}

}

<form action="<c:url value='/loginAction.action'/>" method="post">

用户名0:<input type="text"   name="userList[0].username"/><br/>

用户名1:<input type="text"   name="userList[1].username"/><br/>

密 码0:<input type="password"   name="userList[0].password"/><br/>

密 码1:<input type="password"   name="userList[1].password"/><br/>

<input type="submit"   value="登录"/>

</form>

通过上面的方式也可以把表单数据封装到Map类型的属性中。

<form action="<c:url value='/loginAction.action'/>" method="post">

用户名0:<input type="text"   name="userMap[‘zhangSan’].username"/><br/>

用户名1:<input type="text"   name="userMap[‘liSi’].username"/><br/>

密 码0:<input type="password"   name="userMap[‘zhangSan’].password"/><br/>

密 码1:<input type="password"   name="userList[‘liSi’].password"/><br/>

<input type="submit"   value="登录"/>

</form>

 

四.类型转换(了解)

1 Struts2类型转换器

我们现在已经知道,Struts2可以把数据封装到Action的属性中!但我们知道,请求参数都是字符串类型的,而Action的属性如果是int类型,发现也没有出错,这是因为Struts2有能力把String转换成int类型,这就是类型转换了。

Struts2提供了很多内置的类型转换器,用来把String转换成需要的类型,这些类型转换器基本上就够用了,所以我们很少自定义类型转换器:

l  int和Integer;

l  long和Long;

l  float和Float;

l  double和Double;

l  char和Character;

l  boolean和Boolean;

l  Date(格式为:yyyy-MM-dd)

public class UserAction extends ActionSupport {

public void setMyDate(Date date) {

System.out.println(date);

}

public String login() {

return NONE;

}

}

<form action="<c:url value='/loginAction.action'/>" method="post">

  日期:<input type="text" name="myDate"/><br/>

<input type="submit" value="提交"/>

</form>

2 自定义类型转换器

我们还可以来自定义类型转换器!

类型转换器需要提供把String[]转换成属性类型,还需要把属性类型再转换成String类型。当提交表单时,是把String[]类型转换成属性类型(参数封装),当在页面中显示属性时,是把属性类型转换成String类型(数据回显)。所以我们要知道类型转换器是要支持双向转换的。

实现自定义类型转换器:

l  实现com.opensymphony.xwork2.conversion.TypeConverter(不方便);

l  继承com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter类,重写convertValue()方法(不方便);

l  继承org.apache.struts2.util.StrutsTypeConverter类,重写convertFromString()和convertToString()方法(OK)。

DefaultTypeConverter类实现了TypeConverter接口,我们继承DefaultTypeConverter类是需要覆盖它的convertValue(Map context, Object value, Class toType)方法:

l  Map<String, Object> context:OGNL上下文对象,通常用不上它;

l  Object value:被转换的值;

l  Class toType:要被转换成什么类型。

因为转换是双向的,所以我们需要判断toType是什么类型。通过判断toType的类型就可以知道当前转换的“方向”:

if(toType == String.class) {//数据回显

} else {//请求参数封装

}

  注意,当要做请求参数封装时,value的类型不是String,而是String[]类型。所以你需要获取String[]的第一个参数,即下标0的值进行转换。

我们写一个PersonConverter,它可以把String转换成Person类型,也可以把Person转换成String类型。

public class PersonAction extends ActionSupport {

private Person person;

public Person getPerson() {

return person;

}

public void setPerson(Person person) {

this.person = person;

}

public String execute() {

System.out.println(person);

return NONE;

}

}

public class PersonConverter extends DefaultTypeConverter {

public Object   convertValue(Map<String, Object> context, Object value,

Class toType) {

if(toType == String.class) {

return value.toString();

} else {

String s = ((String[]) value)[0];

String[] strs = s.split(",");

return new Person(strs[0], Integer.parseInt(strs[1]),   strs[2]);

}

}

}

<package name="s5"   namespace="/" extends="struts-default">

<action name="PersonAction" class="cn.itcast.s5.aciton.PersonAction">

<result>/index.jsp</result>

</action>

</package>

<form action="<c:url value='/PersonAction.action'/>"   method="post">

Person:   <input type="text"   name="person"/>

<input type="submit" value="提交"/>

</form>

在继承DefaultTypeConverter类时,我们需要自己去判断toType的类型,然后再做双向的转换,这很不方便,所以Struts2又提供了org.apache.struts2.util.StrutsTypeConverter类,它是DefaultTypeConverter的子类,该类提供了两个抽象方法(convertFromString、convertToString),分别用来参数封装和数据回显!

public class PersonConverter extends StrutsTypeConverter {

// 参数封装,String --> Peson

public Object convertFromString(Map   context, String[] values, Class toClass) {

String[] s = values[0].split(",");

return new Person(s[0], Integer.parseInt(s[1]),   s[2]);

}

// 数据回显    Person --> String

public String convertToString(Map   context, Object value) {

return value.toString();

}

}

注意,上面代码还不能运行,因为Struts2无法找到PersonConverter,我们需要注册PersonConverter,这样Struts2才能找到它。

l  局部注册:只为PersonAction的person属性使用的类型转换器;

l  全局注册:所有Person类型都可以使用的类型转换器。

局部注册:在PersonAction所在包下(即cn.itcast.action)创建名为ActionName-conversion.properties(即PersonAction-conversion.properties)的文件。内容为属性名称=转换器类名(即person=cn.itcast.converter.PersonConverter)。

局部注册只对PersonAction类的person属性使用转换器!!!

全局注册:在src下创建名为xwork-conversion.properties的文件,内容为属性类名=转换器类名(即cn.itcast.Person=cn.itcast.converter.PersonConverter)。

全局注册会对所有Person类型的Action属性使用转换器!!!

3 类型转换的错误信息

当类型转换失败时,会出现NoSuchMethodException异常。这是因为拦截器在无法转换请求参数为目标类型后,会尝试以String[]为种原始的请求参数类型来封装到Action中,但如果Action没有提供这个原始的参数类型的set方法,那么就会报错。例如我们给PersonAction的int age属性赋值为abc时,那么就会出现这个异常:

这个异常是在说,没有找到setAge(String[])方法!因为拦截器无法将abc转换成int类型时,它试图用请求参数的原始类型(String[])来封装到PersonAction中,所以会去找setAge(String[])方法,但没有找到,所以抛出异常。

当出现类型转换错误后,params拦截器会记录这个错误,然后再由conversionError拦截器把错误存放到fieldError(字段错误)中,然后跳转到input结果码,这说明我们需要在<action>中配置<result name=”input>,然后在input页面中使用Struts2的标签<s:fieldError>来获取!

<package name="s5"   namespace="/" extends="struts-default">

<action name="PersonAction" class="cn.itcast.aciton.PersonAction">

<result name="input">/form.jsp</result>

<result>/result.jsp</result>

</action>

</package>

<%@ page language="java"   import="java.util.*" pageEncoding="UTF-8"%>

<%@ taglib prefix="c"   uri="http://java.sun.com/jsp/jstl/core"   %>

<%@ taglib prefix="s"   uri="/struts-tags" %>

<!DOCTYPE HTML   PUBLIC "-//W3C//DTD   HTML 4.01 Transitional//EN">

<html>

<head>

</head>

<body>

<s:fielderror/>

<form action="<c:url value='/PersonAction.action'/>"   method="post">

姓名:<input type="text"   name="name"/><br/>

年龄:<input type="text"   name="age"/><br/>

性别:<input type="text"   name="gender"/><br/>

<input type="submit" value="提交"/>

</form>

</body>

</html>

  我们发现Struts2打印的类型转换错误信息是英文的,这说明我们需要自定义错误信息。自定义错误信息需要在Action所在目录下创建ActionName.properties文件(与Action同名的properties文件),然后在该文件中给出:invalid.fieldvalue.属性名=错误信息,其中invalid.fieldvalue是固定的。例如:invalid.fieldvalue.person=无法将请求参数转换成Person类型!

PersonAction.proeprties

invalid.fieldvalue.person=无法把表单参数转换成Person类型

4 类型转换的流程

五.输入校验

1 什么是输入校验

在Action封装了请求参数后,还需要对其进行校验。例如name不能为空,age只能在18~60之间等等!输入校验是在类型转换成功之后,才可能执行的。

校验分类:

l  JavaScript客户端校验(改善用户体验);

l  服务器端校验(保证安全性),即使用Struts2输入校验。

2 Struts2输入校验分类

Struts2输入校验分为两种:

l  代码方式校验;

l  配置校验:

  • XML配置校验(常用);
  • 注解配置校验。

这里只介绍XML配置校验  其他校验请参考struts2附录

3 XML配置方式校验(最主要)

代码校验缺点:

代码方式校验方式会出现很多冗余。在某个校验方法中出现了“非空”校验逻辑,在另一个校验方法中还需要出现“非空”校验逻辑。

代码校验不方法维护。当需要修改它时就要修改原代码。

使用XML配置方式是先把常用的校验规则写好,然后在XML配置中指定要使用的校验规则。当然Struts2已经帮我们写好了很多的校验规则。我们只需要指在XML文档中配置当前的请求处理方法需要哪些校验规则。

3.1 XML配置方式校验要求

要使用XML配置方式校验,你的Action类必须实现Validateable接口。ActionSupport类已经实现了Validateable接口,所以我们通常是直接继承ActionSupport类。

为属性提供getXXX()和setXXX()方法!代码校验是在Action本类中来完成校验,这说明我们可以直接使用本类的private属性,但如果使用XML配置方式校验,这需要使用校验框架的代码来完成校验工作,那么校验框架需要调用Action的getXXX()方法来获取被校验的属性,所以一定要为被校验的属性提供getXXX()方法。

3.2 创建校验文件

1. 校验文件的命名必须为:ActionName-validation.xml。例如LoginAction的校验文件命名为:LoginAction-validation.xml。

2. 校验文件的路径:必须与Action在同包下。

3. 校验文件的DTD:在xwork-core-x.x.x.jar中找到xwork-validator-x.x.x.dtd,打开它,内部会有一段DTD,我们把它copy过来,放到我们的校验文件中。

xwork-validator-1.0.3.dtd

<?xml version="1.0" encoding="UTF-8"?>

<!--

XWork Validators DTD.

Used the following DOCTYPE.

<!DOCTYPE validators PUBLIC

"-//Apache   Struts//XWork Validator 1.0.3//EN"

"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">

-->

<!ELEMENT validators (field|validator)+>

<!ELEMENT field (field-validator+)>

<!ATTLIST field

name CDATA #REQUIRED

>

<!ELEMENT field-validator (param*, message)>

<!ATTLIST field-validator

type CDATA #REQUIRED

short-circuit (true|false) "false"

>

<!ELEMENT validator (param*, message)>

<!ATTLIST validator

type CDATA #REQUIRED

short-circuit (true|false) "false"

>

<!ELEMENT param (#PCDATA)>

<!ATTLIST param

name CDATA   #REQUIRED

>

<!ELEMENT message (#PCDATA|param)*>

<!ATTLIST message

key CDATA   #IMPLIED

>

4. 为了在MyEclipse中对XML有提供功能,那么还需要让MyEclipse导入DTD文件的位置。

3.3 编写校验文件

校验文件的元素结果如下:

<validators>

<field name="">

<field-validator type="">

<param name=""></param>

<message></message>

</field-validator>

</field>

</validators>

<field>的name属性指定要校验的属性,例如<feld name=”username”>,表示要校验的属性是username属性。

<field-validator>的type属性指定校验规则,校验规则由Struts2提供,Struts2提供的所有校验规则在:

打印default.xml文件,内部如下:

defualt.xml

<?xml version="1.0"   encoding="UTF-8"?>

<!DOCTYPE validators PUBLIC

"-//Apache Struts//XWork Validator   Definition 1.0//EN"

"http://struts.apache.org/dtds/xwork-validator-definition-1.0.dtd">

<!-- START SNIPPET:   validators-default -->

<validators>

<validator name="required"   class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/>

<validator name="requiredstring"   class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"/>

<validator name="int" class="com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator"/>

<validator name="long" class="com.opensymphony.xwork2.validator.validators.LongRangeFieldValidator"/>

<validator name="short" class="com.opensymphony.xwork2.validator.validators.ShortRangeFieldValidator"/>

<validator name="double"   class="com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator"/>

<validator name="date" class="com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator"/>

<validator name="expression"   class="com.opensymphony.xwork2.validator.validators.ExpressionValidator"/>

<validator name="fieldexpression"   class="com.opensymphony.xwork2.validator.validators.FieldExpressionValidator"/>

<validator name="email" class="com.opensymphony.xwork2.validator.validators.EmailValidator"/>

<validator name="url" class="com.opensymphony.xwork2.validator.validators.URLValidator"/>

<validator name="visitor"   class="com.opensymphony.xwork2.validator.validators.VisitorFieldValidator"/>

<validator name="conversion"   class="com.opensymphony.xwork2.validator.validators.ConversionErrorFieldValidator"/>

<validator name="stringlength"   class="com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator"/>

<validator name="regex" class="com.opensymphony.xwork2.validator.validators.RegexFieldValidator"/>

<validator name="conditionalvisitor"   class="com.opensymphony.xwork2.validator.validators.ConditionalVisitorFieldValidator"/>

</validators>

上面文件中每个<validator>元素都是一个校验规则,校验规则对应一些已经写好的方法,他们有校验属性非空的规则,有校验字符串属性长度的规则,有校验int属性范围的规则等等。通常我们不需要自己来编写校验规范,因为上面的校验规则已经足够了。

每个规则都有自己的名字,校验文件中<field-validator>的type就是用来指定校验规则的名称。例如下面的代码是对username属性的非空校验:

<validators>

<field name="username">

<field-validator type="requiredString">

……

</field-validator>

</field>

</validators>

其中type=”requiredString”是校验规则的名称,它必须对应defualt.xml文件中<validator>元素的name属性值。requiredString校验规则是校验字符串属性是否长度为0,如果长度为0,它会向fieldError中添加错误信息。

<message>元素指定的是错误信息,例如:

<validators>

<field name="username">

<field-validator type="requiredstring">

<message>用户名不能为空</message>

</field-validator>

</field>

</validators>

每个校验规则还都有自己的参数,如果想知道每个校验规则有什么参数,那么最好的方法是去查看校验规则的源代码。

例如requiredstring校验规则有一个trim参数,它是boolean类型的参数,当trim为true时,requiredString校验器会先调用属性的trim()方法(去掉前后空白),然后再校验长度是否为0。trim参数的默认值就是true。

public class RequiredStringValidator extends FieldValidatorSupport {

private boolean doTrim = true;

public void setTrim(boolean trim) {

doTrim = trim;

}

public boolean getTrim() {

return doTrim;

}

public void validate(Object object) throws   ValidationException {

String fieldName = getFieldName();

Object value = this.getFieldValue(fieldName,   object);

if (!(value instanceof String)) {

addFieldError(fieldName, object);

} else {

String s = (String) value;

if (doTrim) {

s = s.trim();

}

if (s.length() == 0) {

addFieldError(fieldName,   object);

}

}

}

}

  下面是完整的LoginActoin的校验文件:

LoginAction-validation.xml

<validators>

<field name="username">

<field-validator type="requiredstring">

<!-- 调用校验器的setTrim()方法传递参数false -->

<param name="trim">false</param>

<message>用户名不能为空</message>

</field-validator>

<field-validator type="stringlength">

<!-- 调用校验器的setMaxLength()方法传递参数16 -->

<param name="maxLength">16</param>

<!-- 调用校验器的setMinLength()方法传递参数3 -->

<param name="minLength">3</param>

<message>用户名长度必须在3~16之间</message>

</field-validator>

</field>

<field name="password">

<field-validator type="requiredstring">

<param name="trim">false</param>

<message>密码不能为空</message>

</field-validator>

</field>

</validators>

3.4 校验规则介绍

l  required:当属性为null时校验失败;

l  requiredstring:当字符串属性为null或长度为0时校验失败:

  • 参数trim:默认值为true,表示去除前后空白后再校验长度。

l  stringlength:当字符串长度不在指定范围内时校验失败:

  • minLength:指定字符串的最小长度;
  • maxLength:指定字符串的最大长度。

l  regex:属性不能匹配正则表达式时校验失败:

  • expression:指定正则表达式;
  • caseSensitive:默认值为true,表示不忽略大小写。

l  int:当int属性不在指定范围内校验失败:

  • min:最小值;
  • max:最大值。

l  double:当double属性不在指定范围内校验失败:

  • min:最小值;
  • max:最大值。

l  fieldexpression:属性必须是OGNL表达式:

  • expression:用来校验的ONGL表达式,例如pass == repass,其中pass和repass是两个属性名称,当这两个属性的值相等时校验通过,否则失败。

l  email:属性必须是合法的邮件地址;

l  url:属性必须是合法的网址;

l  date:属性必须是合法的日期格式。

六.拦截器

1 理解拦截器

来自AOP(面向切面)思想。它看起来与JavaWeb中的Filter极其相似。我们已经知道请求会先过一系列拦截器,最终到达Action,或者中途中断。也就是说,每个拦截器都有中断请求的能力。

因为现在还不是讲AOP的时候,所以我们现在只要把拦截器理解为JavaWeb中Filter即可。你回忆一下Filter与Servlet的关系,那么Interceptor与Action就是相同的关系。

2 Struts2中的拦截器

  在Struts2中定义了很多拦截器,你可以去struts-default.xml文件中查看。

自定义拦截器

注意:当拦截器拦截,返回结果集后,则后面的都不会执行,直接返回结果集。

我们也可以自定义拦截器,Struts2要求所有拦截器必须实现Interceptor接口。

Interceptor.java

public interface Interceptor extends Serializable {

void destroy();

void init();

String intercept(ActionInvocation   invocation) throws Exception;

}

Struts2还提供了一个Interceptor接口的实现类:AbstractInterceptor,通常我们自定义拦截器都是通过继承AbstractInterceptor类,而不是实现Interceptor接口。

AbstractInterceptor.java

public abstract class AbstractInterceptor implements Interceptor {

public void init() {}

public void destroy() {}

public abstract String   intercept(ActionInvocation invocation) throws Exception;

}

继承AbstractInterceptor类时,不需要“被迫”实现init()和destroy()方法,而只需要关注intercept()方法即可。

1、执行效率统计拦截器

建立拦截器ElapsedTimeInterceprot实现Interceptor接口

//动作方法及结果处理耗时统计拦截器

public class ElapsedTimeInterceprot implements Interceptor {

public String intercept(ActionInvocation invocation) throws Exception {

long beginTime = System.nanoTime();//纳秒:1毫秒=1000000纳秒

String result = invocation.invoke();//放行:拦截前要做的事放在invocation.invoke()之前,拦截后放在之后

//结果处理完毕后执行

long endTime = System.nanoTime();

System.out.println(invocation.getInvocationContext().getName()+"动作执行耗时:"+(endTime-beginTime)+"纳秒");

return result;

}

public void destroy() {

}

public void init() {

}

}

进行配置文件设置

<package name="p1" extends="struts-default">

<interceptors>

<interceptor name="elapsedTime" class="com.itheima.interceptors.ElapsedTimeInterceprot"></interceptor>

</interceptors>

<action name="test1" class="com.itheima.action.HelloAction1" method="test1">

<!-- 需要获取request,所以必须先执行servletConfig拦截器 -->

<interceptor-ref name="defaultStack"></interceptor-ref>

<!-- 执行耗时统计拦截器 -->

<interceptor-ref name="elapsedTime"></interceptor-ref>

<result>/1.jsp<result>

</action>

</package>

拦截器的扩展,定义拦截器小组

<package name="p1" extends="struts-default">

<interceptors>

<interceptor name="elapsedTime" class="com.itheima.interceptors.ElapsedTimeInterceprot"></interceptor>

<!-- 自己定义一个拦截器小组 -->

<interceptor-stack name="myDefaultStack">

<interceptor-ref name="defaultStack"></interceptor-ref>

<interceptor-ref name="elapsedTime"></interceptor-ref>

</interceptor-stack>

</interceptors>

<action name="test1" class="com.itheima.action.HelloAction1" method="test1">

<!-- 执行自定义的拦截器小组 -->

<interceptor-ref name="myDefaultStack"></interceptor-ref>

<result>/1.jsp<result>

</action>

</package>

继续扩展配置设置

<package name="mypackage" extends="struts-default" abstract="true">

<interceptors>

<interceptor name="elapsedTime" class="com.itheima.interceptors.ElapsedTimeInterceprot"></interceptor>

<interceptor name="sessionCheck" class="com.itheima.interceptors.SessionCheckInterceptors">

<!-- 说明test2动作方法不需要拦截 -->

<param name="excludeMethods">test2</param>

</interceptor>

<interceptor-stack name="myDefaultStack">

<interceptor-ref name="defaultStack"></interceptor-ref>

<interceptor-ref name="elapsedTime"></interceptor-ref>

<interceptor-ref name="sessionCheck"></interceptor-ref>

</interceptor-stack>

</interceptors>

<!-- 设置该包中的所有action配置默认拦截器 ,每个包只能指定一个默认拦截器 -->

<default-interceptor-ref name="myDefaultStack"></default-interceptor-ref>

</package>

2、是否登陆拦截器MethodFilterInterceptor(可以配置是否进行拦截excludeMethods属性,如上)

权限判断拦截器继承MethodFilterInterceptor类,这样只对某些方法起作用,而对其他方法不起作用。 (配置文件如上)

//执行动作方法前检查用户是否已经登录

public class SessionCheckInterceptors extends MethodFilterInterceptor{

protected String doIntercept(ActionInvocation invocation) throws Exception {

String result = "login";//对应的就是一个结果

HttpSession session = ServletActionContext.getRequest().getSession();

User user = (User)session.getAttribute("user");

if(user!=null)

//如果用户有登录,则放行。

result = invocation.invoke();

//如果用户没有登录,则返回结果集。

return result;

}

}

配置文件需要在结果集中增加一个

<result name="login">/login.jsp</result>

 注册拦截器

注册拦截器一共分为两步:

l  在<package>中声明拦截器;

l  在<action>中引用拦截器。

<package name="s8"   namespace="/" extends="struts-default">

<interceptors>

<interceptor name="MyInterceptor" class="cn.itcast.interceptor.MyInterceptor"   />

</interceptors>

<action name="LoginAction">

<result>/index.jsp</result>

<result name="input">/login.jsp</result>

<interceptor-ref name="MyInterceptor" />

</action>

</package>

上面的代码虽然可以执行MyInterceptor了,但因为Struts2有这么一种机制,一旦为Action指定了拦截器,那么就不会再为这个Action执行默认拦截器了,即defaultStack这个拦截器栈中的拦截器都不会执行,也就是说,这个Action没有输入校验、没有参数注入、没有国际化、没有…,这是不行的,所以我们需要在这个<action>元素中再引用defaultStack拦截器栈。

<package name="s8"   namespace="/" extends="struts-default">

<interceptors>

<interceptor name="MyInterceptor" class="cn.itcast.interceptor.MyInterceptor"   />

</interceptors>

<action name="LoginAction">

<result>/index.jsp</result>

<result name="input">/login.jsp</result>

<interceptor-ref name="defaultStack" />

<interceptor-ref name="MyInterceptor" />

</action>

</package>

在<aciton>元素中引用拦截器的顺序决定了拦截器的执行顺序,上例中会先执行defaultStack中的所有拦截器,再执行MyInterceptor拦截器。

上面的方式虽然可以注册拦截器,但比较麻烦。因为如果当前包中所有<action>都需要执行MyInterceptor拦截器,那么就需要在每个<action>元素中引入拦截器。其实还有另一种方式,就是为当前包指定默认拦截器栈!

我们都知道,因为我们的包继承了struts-default包,所以默认的拦截器栈是defaultStack,但没有为<action>元素指定拦截器时,那么就会执行defaultStack拦截器栈。我们可以在<package>中声明一个拦截器栈,然后在去替换默认拦截器栈即可。

<package name="s8"   namespace="/" extends="struts-default">

<interceptors>

<interceptor name="MyInterceptor" class="cn.itcast.interceptor.MyInterceptor"   />

<interceptor-stack name="myStack">

<interceptor-ref name="defaultStack" />

<interceptor-ref name="MyInterceptor" />

</interceptor-stack>

</interceptors>

<default-interceptor-ref name="myStack" />

<action name="LoginAction">

<result>/index.jsp</result>

<result name="input">/login.jsp</result>

</action>

</package>

八.上传

1        上传下载组件介绍

l  jspSmartUpload(model1的年代);

l  apache-commons-fileupload,Struts2默认上传组件;

l  Servlet3.0使用的Part,但Servlet3.0还没有普及;

l  COS,Struts2支持,不过已经停止更新很久了;

l  pell,Struts2支持。

2        fileUpload的拦截器

Struts2默认使用的是commons-fileUpload组件完成上传的,使用Struts2会大量简化上传文件的开发。这一工作由fileUpload拦截器来完成。它会查看当前请求的enctype是否为multipart/form-data,如果不是就会直接“放行”;如果是,那么它会去解析表单,然后把解析的结果传递给Action的属性!

  fileUpload拦截器对会对Action提供很大的“帮助”,同时它也会对Action提出一些“小小的要求”。Action需要提供3个属性:

l  File fieldName

l  String fieldNameContentType

l  String fieldNameFileName;

三个属性的前缀都(fieldName)必须与文件表单项名称一致,例如有文件表单项内容为:<input type=”file” name=”myUpload”/>,其中表单项名称为:myUpload,那么Action就必须要有如下3个属性:

l  private File myUpload

l  private String myUploadContentType

l  private String myUploadFileName

3 上传配置

可以通过Struts2的常量来完成对上传的配置,下面是与上传相关的常量:

l  struts.multipart.parser:指定使用的上传组件,默认值为jakarta,表示使用commons-fileupload组件,Struts2还支持cos和pell;

l  struts.multipart.saveDir:临时目录,如果没有指定临时目录,那么临时文件会在Tomcat的work目录中;

l  struts.multipart.maxSize:整个大小限制,默认值为2097152,即2M。注意,这个限制是整个请求的大小,而不是单一文件的大小。

当上传的表单超出了限制时,拦截器会向actionError中添加错误信息!当执行wokflow拦截器时,会发现actionError中存在错误,这时就会跳转到input视图,所以我们需要为Action指定input视图。

fileUpload拦截器也有3个参数,我们可以给fileUpload拦截器配置这3个参数:

l  maximumSize:上传的单个文件的大小限制;

l  allowedTypes:允许上传文件的类型,多个类型以逗号隔开;

l  allowedExtensions:允许上传文件的扩展名,多个扩展名以逗号隔开;

<struts>

<constant name="struts.devMode"   value="true" />

<constant name="struts.multipart.maxSize"   value="1048576" />

<package name="s8"   namespace="/" extends="struts-default">

<action name="UploadAction" class="cn.itcast.upload.action.UploadAction">

<result name="input">/demo1/upload.jsp</result>

<param name="savepath">/WEB-INF/uploads</param>

<interceptor-ref name="defaultStack">

<!-- 限制单个文件大小上限为512K -->

<param name="fileUpload.maximumSize">524288</param>

<param name="fileUpload.allowedExtensions">jpg,png,bmp</param>

</interceptor-ref>

</action>

</package>

</struts>

4 上传相关的错误信息国际化

在上传文件时如果出现错误,那么在input视图显示的错误信息都是英文的。如果想替换这些信息,需要知道这些错误信息的资源key,然后在我们自己的国际化资源文件中指定这些key的新值即可。

与上传相关的错误信息都在org.apache.struts2包下的struts-message.properties文件中。

struts.messages.error.uploading=Error   uploading: {0}

struts.messages.error.file.too.large=The   file is to large to be uploaded: {0} "{1}" "{2}" {3}

struts.messages.error.content.type.not.allowed=Content-Type   not allowed: {0} "{1}" "{2}" {3}

struts.messages.error.file.extension.not.allowed=File   extension not allowed: {0} "{1}" "{2}" {3}

struts.messages.upload.error.SizeLimitExceededException=Request   exceeded allowed size limit! Max size allowed is: {0} but request was: {1}!

  我们可以在src下res.properties文件,在这个文件中对象以上资源key进行替换。然后在struts.xml文件中给出<constant name="struts.custom.i18n.resources" value="res/">即可。

5 多文件上传

当需要上传多个文件时,如果每个<input type=”file”>对应Action的3个属性,那么这会使Action的属性过多的现象。处理这一问题的方法是在表单中设置所有<input type=”file”>的名称为相同名称,然后在Action中给出数组属性即可。

九.文件下载

1 回忆JavaWeb文件下载

其实也可以让用户通过浏览器直接访问下载资源,但这样程序无法控制下载流程了。所以在JavaWeb中通过Servlet完成下载。

下载是与响应相关的操作,在JavaWeb中下载要做如下操作:

l  设置Content-type响应头,其实这个头你不设置浏览器也会自己通过文件名来识别;

l  设置Disposition响应头,它默认值是inline,表示在页面中打开,下载时应该设置为attachment,表示让浏览器弹出下载框;

l  处理文件中乱码问题;

l  获取被下载文件,把被下载文件的内容写入到response.getOutputStream()流中;

2 Struts2结果类型之stream

在Struts2中,所有与响应相关的工作都由<result>来处理。我们知道,<result>的默认类型为dispatcher,表示请求转发到JSP,如果设置<result>的type为redirect,那么表示重定向。也就是说所有和响应相关的事情都交给<result>元素指定的类来处理。

Struts2专门提供了一个stream类型的Result,它用来完成下载。也就是说在下载时我们的Action中给出的<result>的type类型应该指定为stream。stream结果对应的结果类型为StreamResult类!这个类来完成设置响应头,以及向response.getOutputStream()中写入数据。

StreamResult类对Content-type提供的默认值为text/plain,这不是我们想要的,所以我们需要给StreamResult设置新的Cotnent-type。

StreamResult类对disposition头提供的默认值为inline,这也不是我们想要的,所以我们也需要给它设置为attachment。

在StreamResult类中提供了两个属性:contentType和contentDisposition,我们需要给这两个属性赋值,来替换默认值。

StreamResult类还要求Action提供一个getInputStream()方法,它会通过这个方法来获取一个InputStream对象,它会把这个流中的内容写入到response.getOutputStream()中。

3 OGNL表达式获取Action属性值

  上例中要下载的文件是硬编码,文件的ContentType类型是硬编码,下载框中的文件名也是硬编码,这些都是需要处理的。

<action name="Download1Action" class="cn.itcast.download.action.Download1Action">

<param name="downloadDir">/WEB-INF/downloads</param>

<result type="stream" name="success">

<param name="contentType">${contentType}</param>

<param name="contentDisposition">attachment;filename=${filename}</param>

</result>

</action>

Download1Action

public class Download1Action extends ActionSupport {

private String downloadDir;

private InputStream inputStream;

private String filename;

public void setDownloadDir(String   downloadDir) {

this.downloadDir = ServletActionContext.getServletContext().getRealPath(downloadDir);

}

public void setFilename(String filename)   throws UnsupportedEncodingException {

// 处理GET请求中文编码问题

this.filename = new   String(filename.getBytes("iso-8859-1"), "utf-8");

}

public String getFilename() throws   UnsupportedEncodingException {

// 在下载框中显示的文件名需要处理编码问题

return new String(filename.getBytes("GBK"), "iso-8859-1");

}

public String getContentType() {

// 文件的MIME类型

return ServletActionContext.getServletContext().getMimeType(filename);

}

public InputStream getInputStream()   throws FileNotFoundException {

return this.inputStream;

}

public String execute() throws Exception   {

File file = new File(downloadDir, filename);

inputStream = new   FileInputStream(file);

return SUCCESS;

}

}

struts2进阶的更多相关文章

  1. Struts2进阶(一)运行原理及搭建步骤

    Struts2进阶(一)运行原理 Struts2框架 Struts2框架搭建步骤 致力于web服务,不可避免的涉及到编程实现部分功能.考虑使用到SSH框架中的Struts2.本篇文章只为深入理解Str ...

  2. Struts2进阶学习4

    Struts2进阶学习4 自定义拦截器的使用 核心配置文件 <?xml version="1.0" encoding="UTF-8"?> <! ...

  3. Struts2进阶学习3

    Struts2进阶学习3 OGNL表达式与Struts2的整合 核心配置文件与页面 <?xml version="1.0" encoding="UTF-8" ...

  4. SSH开发模式——Struts2进阶

    在之前我有写过关于struts2框架的博客,好像是写了三篇,但是之前写的内容仅仅是struts2的一些基础知识而已,struts2还有很多有趣的内容等待着我们去发掘.我准备再写几篇关于struts2的 ...

  5. struts2进阶篇(2)

    一.Action与MVCstruts2是一个基于MVC的web应用框架,它将应用程序分为三个组件:模型,视图,控制器.模型:包含了应用程序的业务逻辑和业务数据,由封装数据和处理业务的javaBean组 ...

  6. struts2进阶篇(5)

    一.OGNL简介 OGNL (Object-Graph Navigation Language)的缩写,简称对象图导航语言. OGNL表达式的特特点: >能够取对象的属性,也能调用对象的方法. ...

  7. struts2进阶篇(4)

    一.使用ActionContext访问Servlet API strtus2提供了一个ActionContext类,该类别称为Action上下文或者Action环境,Action可以通过该类来访问最常 ...

  8. struts2 进阶--异常捕获机制

    在SpringMvc中有自己的异常处理机制,struts2当然会有此功能,主要是在struts.xml中配置: <bean type="com.opensymphony.xwork2. ...

  9. J2EE进阶(二)从零开始之Struts2

    J2EE进阶(二)从零开始之Struts2 以前自己总是听说什么SSH框架,不明觉厉.现在自己要重整旗鼓,开始系统性的学习SSH框架了.首先开始Struts2的学习.其实自己之前参与过Struts2项 ...

随机推荐

  1. fix34

    public int[] fix34(int[] nums) { int i3=0; int i4=0; int temp=0; while( (i3<nums.length)&& ...

  2. 《Cracking the Coding Interview》——第13章:C和C++——题目2

    2014-04-25 19:29 题目:对比一下哈希表和STL中的map的区别,哈希表如何实现?如果数据规模比较小,可以用什么来代替哈希表? 解法:哈希表可以理解为一堆桶,每个桶都有唯一的id,桶里可 ...

  3. 【Validation】林轩田机器学习基石

    这一节主要讲如何通过数据来合理的验证模型好不好. 首先,否定了Ein来选模型和Etest来选模型. (1)模型越复杂,Ein肯定越好:但是Eout就不一定了(见上一节的overfitting等) (2 ...

  4. 利用socket.io构建一个聊天室

    利用socket.io来构建一个聊天室,输入自己的id和消息,所有的访问用户都可以看到,类似于群聊. socket.io 这里只用来做一个简单的聊天室,官网也有例子,很容易就做出来了.其实主要用的东西 ...

  5. 使用pip命令报You are using pip version 9.0.3, however version 18.0 is available pip版本过期.解决方案

    使用pip命令安装或卸载第三方库时报You are using pip version 9.0.3, however version 18.0 is available.错误,一般情况下是pip版本过 ...

  6. RabbitMQ vhost 配置

    RabbitMQ vhost 配置 rabbitmqctl set_vhost_limits是用来定义虚拟主机限制的命令 配置最大连接限制 要限制vhost vhost_name中并发客户端连接的 总 ...

  7. SQLAlchemy技术文档(中文版)(中)

    10.建立联系(外键) 是时候考虑怎样映射和查询一个和Users表关联的第二张表了.假设我们系统的用户可以存储任意数量的email地址.我们需要定义一个新表Address与User相关联. from ...

  8. P4342 [IOI1998]Polygon

    题意翻译 题目可能有些许修改,但大意一致 多边形是一个玩家在一个有n个顶点的多边形上的游戏,如图所示,其中n=4.每个顶点用整数标记,每个边用符号+(加)或符号*(乘积)标记. 第一步,删除其中一条边 ...

  9. [转]Linux下阅读源代码:(g)vim+Taglist+ctags

      Linux下阅读源代码的方法很多,聪明人从标题应该就可以知道,需要(g)vim+Taglist+ctags.3者配合,真是珠联璧合,功力无限啊! vim/gvim什么是vim/gvim,如果看官连 ...

  10. 【BZOJ 5048 塌陷的牧场】

    Time Limit: 25 Sec  Memory Limit: 256 MBSubmit: 77  Solved: 34[Submit][Status][Discuss] Description ...