day02
下面是在每个Action之前都会执行的拦截器,这段代码来自与struts-default.xml文件。
<interceptor-stack name="defaultStack"> <interceptor-ref name="exception"/> <interceptor-ref name="alias"/> <interceptor-ref name="servletConfig"/> <interceptor-ref name="i18n"/> <interceptor-ref name="prepare"/> <interceptor-ref name="chain"/> <interceptor-ref name="scopedModelDriven"/> <interceptor-ref name="modelDriven"/> <interceptor-ref name="fileUpload"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="staticParams"/> <interceptor-ref name="actionMappingParams"/> <interceptor-ref name="params"> <param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</param> </interceptor-ref> <interceptor-ref name="conversionError"/> <interceptor-ref name="validation"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="workflow"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="debugging"/> </interceptor-stack> |
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都不是单例的!可以通过给UserAction添加构造器来测试!
这种方式我们只需要做:让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[崔1] "/><br/> 密 码:<input type="password" name="user.password[崔2] "/><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()[崔3] ; @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 [崔4] == 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]); [崔5] } } } |
<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-convsion.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>[崔6] <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" %>[崔7] <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> </head> <body> <s:fielderror/>[崔8] <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配置校验(常用);
- 注解配置校验。
3 代码方式的输入校验
覆盖ActionSupport类的validate()方法,在这个方法中完成对参数的校验。validate()方法会在参数封装之后,在execute()方法之前执行。如果validate()方法中向fieldError中添加了错误信息,那么就不会再执行execute()方法,而是跳转到input结果码对应的页面。
<s:fielderror /> <form action="<c:url value='/Demo1Action.action'/>" > 用户名:<input type="text" name="username"/><br/> 密 码:<input type="password" name="password"/><br/> <input type="submit" value="提交"/> </form> |
public class Demo1Action extends ActionSupport { private String username; private String password; @Override public void validate() { if(username == null || username.trim().length() == 0) { this.addFieldError("username", "用户名不能为空"); } if(password == null || password.trim().length() == 0) { this.addFieldError("password", "密码不能为空"); } } public String execute() { System.out.println(username + ", " + password); return NONE; } } |
4 私有校验方法
我们都知道,一个Action中可以存在多个请求处理方法,不同的请求处理方法应该有不同的校验逻辑,所以我们应该为每个请求处理方法提供自己独有的校验方法。而validate()方法是所有请求处理方法公共的校验方法。
Struts2提供的私有校验方法必须满足如下规则:
l public的,没有返回值,没有参数(public void xxx());
l 方法名称前缀为validate(public void validateXxx());
l 方法名后缀必须与请求处理方法名相同,例如请求处理方法为login(),那么它的私有校验方法为:public validateLogin()。
注意,私有校验方法会在公共校验方法(validate())之前被调用。如果你要为execute()提供私有校验方法,那么这个方法名为validateExecute()。
public class Demo2Action extends ActionSupport { public void validateLogin() { System.out.println("validateLogin()..."); } public void validateRegist() { System.out.println("validateRegist()..."); } public void validate() { System.out.println("validate()..."); } public String login() { System.out.println("login()"); return NONE; } public String regist() { System.out.println("regist()"); return NONE; } } |
上面代码的执行顺序为:
l validateLogin();
l validate();
l login():如果在校验方法中调用了addFieldError()方法,那么就不会再执行login()方法。
5 XML配置方式校验(最主要)
使用代码方式校验方式会出现很多冗余。在某个校验方法中出现了“非空”校验逻辑,在另一个校验方法中还需要出现“非空”校验逻辑。
使用代码校验不方法维护。当需要修改它时就要修改原代码。
使用XML配置方式是先把常用的校验规则写好,然后在XML配置中指定要使用的校验规则。当然Struts2已经帮我们写好了很多的校验规则。我们只需要指在XML文档中配置当前的请求处理方法需要哪些校验规则。
5.1 XML配置方式校验要求
要使用XML配置方式校验,你的Action类必须实现Validateable接口。ActionSupport类已经实现了Validateable接口,所以我们通常是直接继承ActionSupport类。
为属性提供getXXX()和setXXX()方法!代码校验是在Action本类中来完成校验,这说明我们可以直接使用本类的private属性,但如果使用XML配置方式校验,这需要使用校验框架的代码来完成校验工作,那么校验框架需要调用Action的getXXX()方法来获取被校验的属性,所以一定要为被校验的属性提供getXXX()方法。
5.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"> [崔9] --> <!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文件的位置。
5.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[崔10] (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> |
5.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:属性必须是合法的日期格式。
5.5 完整的校验案例
<body> <s:fielderror /> <form action="<c:url value='/TestAction.action'/>" > ID:<input type="text" name="id"/><br/> 用户名:<input type="text" name="username"/><br/> 密 码:<input type="password" name="password"/><br/> 确认密码:<input type="password" name="repassword"/><br/> Email:<input type="text" name="email"/><br/> 生日:<input type="text" name="birthday"/><br/> 年龄:<input type="text" name="age"/><br/> 主页:<input type="text" name="homepage"/><br/> <input type="submit" value="提交"/> </form> </body> |
public class TestAction extends ActionSupport { private String id; private String username; private String password; private String repassword; private String email; private Integer age; private String homepage; private Date birthday; … } |
// TestAction-validation.xml <validators> <field name="id"> <field-validator type="requiredstring"> <message>学号不能为空</message> </field-validator> <field-validator type="regex"> <param name="expression">ITCAST_\d{4}</param> <message>学号格式必须是"ITCAST_0000"</message> </field-validator> </field> <field name="username"> <field-validator type="requiredstring"> <message>用户名不能为空</message> </field-validator> <field-validator type="stringlength"> <param name="minLength">3</param> <param name="maxLength">10</param> <message>用户名长度必须在${minLength}~${maxLength}之间</message> </field-validator> </field> <field name="password"> <field-validator type="requiredstring"> <message>密码不能为空</message> </field-validator> <field-validator type="stringlength"> <param name="minLength">3</param> <param name="maxLength">10</param> <message>密码长度必须在${minLength}~${maxLength}之间</message> </field-validator> </field> <field name="repassword"> <field-validator type="requiredstring"> <message>确认密码不能为空</message> </field-validator> <field-validator type="fieldexpression"> <param name="expression">password == repassword</param> <message>两次密码输入不一致</message> </field-validator> </field> <field name="age"> <field-validator type="required"> <message>年龄不能为空</message> </field-validator> <field-validator type="int"> <param name="min">18</param> <param name="max">80</param> <message>年龄必须在${min}~${max}之间</message> </field-validator> </field> <field name="email"> <field-validator type="requiredstring"> <message>Email不能为空</message> </field-validator> <field-validator type="email"> <message>邮件地址格式错误</message> </field-validator> </field> <field name="homepage"> <field-validator type="requiredstring"> <message>主页不能为空</message> </field-validator> <field-validator type="url"> <message>主页地址格式错误</message> </field-validator> </field> <field name="birthday"> <field-validator type="required"> <message>生日不能为空</message> </field-validator> <field-validator type="date"> <param name="min">1940-01-01</param> <param name="max">2013-01-01</param> <message>生日必须在${min}到${max}之间</message> </field-validator> </field> </validators> |
6 XML配置校验方式之私有校验(指定请求处理方法的校验文件)
上面的配置方式是公共的,也就是说一个Action中多个请求方法共享同一个配置文件。如果想为某个特定的请求方法指定XML校验文件,那么这时对校验文件的名称格式就严格。
ActionName-<action name>-validation.xml
注意,文件名称一共分为三部分:
l 第一部分:Action类型;
l 第二部分:<action>的name;
l 第三部分:validation
<action name="hello[崔11] " class="cn.itcast.validation.demo4.TestAction1" method="test"> <result name="input">/demo4/login.jsp</result> <result>/index.jsp</result> </action> |
public class TestAction1[崔12] extends ActionSupport { public String test() { return NONE; } } |
TestAction1-hello-validation.xml
国际化
1 什么是国际化
一款软件可以为不同国家的来访者提供不同语言的界面,那么这个软件就是国际化的。这需要为每种上语言提供资源包(很多语言包),程序通过来访者的国家和语言来定位资源包。
基本名称_语言_国家.properties
例如:res_zh_CN.properties、res_en_US.properties
2 Struts2国际化文件分类
l 全局国际化文件:整个程序都可以使用(最常用)
l 特定包中可以使用(一个包中所有Action可以使用);
l 特定Action中可以使用(一个Action可以使用);
l 临时信息文件(JSP中i18n标签中可以使用)
3 国际化信息的应用场景
l JSP页面中使用国际化信息;
l Action中使用国际化信息;
l 配置文件中使用国际化信息。
4 全局国际化
res._zh_CN.properties
#用户名 username=\u7528\u6237\u540D #用户名不能为空 error.username=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A |
- 指定资源文件路径:
在struts.xml文件中指定资源文件的路径:
<constant name="struts.custom.i18n.resources" value="res/">
这说明在src目录下需要有res_语言_国家.properties文件。
<constant name="struts.custom.i18n.resources" value="cn.itcast.action.res/">
这说明在src/cn/itcast/action目录下需要有res_语言_国家.properties文件。
- 在JSP中使用<s:text>标签获取资源文件信息
在JSP中使用
<s:fielderror /> <form action="<c:url value='Demo1Action.action'/>" method="post"> <s:text name="username"/>[崔13] :<input type="text" name="username"/><br/> <input type="submit" value="提交"/> </form> |
在Action中使用
public class Demo1Action extends ActionSupport { private String username; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String execute() throws Exception { System.out.println(this.getText("error.username")[崔14] ); return NONE; } } |
在配置文件中使用,例如在校验文件中使用。
<validators> <field name="username"> <field-validator type="requiredstring"> <message key="error.username" [崔15] /> </field-validator> </field> </validators> |
5 局部国际化文件
全局国际化文件是整个程序中都可以使用的,文件的位置需要在struts.xml文件中指定。而局部国际化文件不需要在struts.xml文件中指定,但文件的名称,以及位置是固定的。
局部国际化文件分为两种:特定Action中可以使用,以及特定包及子包中可以使用。
Action国际化文件
例如想提供一个只能在cn.itcast.action.TestAction中使用的国际化文件,那么就需要在cn.itcast.action包下创建TestAction_zh_CN.properties文件,这个文件的基本名称必须与Action的名称相同。
包国际化文件
例如想提供一个只能在cn.itcast.action包中使用的国际化文件,那么就需要在cn.itcast.action包下创建package_zh_CN.properties文件。这个文件也可以在cn.itcast.action包的子包中被使用。例如有Actoin的命名为:cn.itcast.action.demo1.Demo1Action,在这个Action中也可以cn.itcast.action.package_zh_CN.properties文件。
6 临时国际化文件
临时国际化文件是在<s:i18n>标签中使用。
<s:i18n name="cn.itcast.demo1.action.res"> <s:text name="username" /> </s:i18n> |
<s:i18n>标签的name属性指定国际化文件的位置,这说明需要在src/cn.itcast.demo1/action/目录下需要有res_zh_CN.properties文件。
7 在国际化文件中使用占位符
在国际化文件中使用占位符,例如在国际化文件中有如下内容:
login.error={0}或{1}不能为空
其中{0}和{1}就是点位符,这需要指定值来替换占位符。
String s = this.getText(“login.error”, new String[]{“用户名”, “密码”});
System.out.println(s);//用户名或密码不能为空。
拦截器
1 理解拦截器
来自AOP(面向切面)思想。它看起来与JavaWeb中的Filter极其相似。我们已经知道请求会先过一系列拦截器,最终到达Action,或者中途中断。也就是说,每个拦截器都有中断请求的能力。
因为现在还不是讲AOP的时候,所以我们现在只要把拦截器理解为JavaWeb中Filter即可。你回忆一下Filter与Servlet的关系,那么Interceptor与Action就是相同的关系。
2 Struts2中的拦截器
在Struts2中定义了很多拦截器,你可以去struts-default.xml文件中查看。
<package name="struts-default[崔16] " abstract="true"> <result-types>[崔17] <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/> <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/> <result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/> <result-type name="httpheader" class="org.apache.struts2.dispatcher.HttpHeaderResult"/> <result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/> <result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/> <result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/> <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/> <result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/> <result-type name="plainText" class="org.apache.struts2.dispatcher.PlainTextResult" /> </result-types> <interceptors>[崔18] <interceptor name="alias" class="com.opensymphony.xwork2.interceptor.AliasInterceptor"/> <interceptor name="autowiring" class="com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor"/> <interceptor name="chain" class="com.opensymphony.xwork2.interceptor.ChainingInterceptor"/> <interceptor name="conversionError" class="org.apache.struts2.interceptor.StrutsConversionErrorInterceptor"/> <interceptor name="cookie" class="org.apache.struts2.interceptor.CookieInterceptor"/> <interceptor name="clearSession" class="org.apache.struts2.interceptor.ClearSessionInterceptor" /> <interceptor name="createSession" class="org.apache.struts2.interceptor.CreateSessionInterceptor" /> <interceptor name="debugging" class="org.apache.struts2.interceptor.debugging.DebuggingInterceptor" /> <interceptor name="execAndWait" class="org.apache.struts2.interceptor.ExecuteAndWaitInterceptor"/> <interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/> <interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/> <interceptor name="i18n" class="com.opensymphony.xwork2.interceptor.I18nInterceptor"/> <interceptor name="logger" class="com.opensymphony.xwork2.interceptor.LoggingInterceptor"/> <interceptor name="modelDriven" class="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"/> <interceptor name="scopedModelDriven" class="com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor"/> <interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/> <interceptor name="actionMappingParams" class="org.apache.struts2.interceptor.ActionMappingParametersInteceptor"/> <interceptor name="prepare" class="com.opensymphony.xwork2.interceptor.PrepareInterceptor"/> <interceptor name="staticParams" class="com.opensymphony.xwork2.interceptor.StaticParametersInterceptor"/> <interceptor name="scope" class="org.apache.struts2.interceptor.ScopeInterceptor"/> <interceptor name="servletConfig" class="org.apache.struts2.interceptor.ServletConfigInterceptor"/> <interceptor name="timer" class="com.opensymphony.xwork2.interceptor.TimerInterceptor"/> <interceptor name="token" class="org.apache.struts2.interceptor.TokenInterceptor"/> <interceptor name="tokenSession" class="org.apache.struts2.interceptor.TokenSessionStoreInterceptor"/> <interceptor name="validation" class="org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor"/> <interceptor name="workflow" class="com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor"/> <interceptor name="store" class="org.apache.struts2.interceptor.MessageStoreInterceptor" /> <interceptor name="checkbox" class="org.apache.struts2.interceptor.CheckboxInterceptor" /> <interceptor name="profiling" class="org.apache.struts2.interceptor.ProfilingActivationInterceptor" /> <interceptor name="roles" class="org.apache.struts2.interceptor.RolesInterceptor" /> <interceptor name="annotationWorkflow" class="com.opensymphony.xwork2.interceptor.annotations.AnnotationWorkflowInterceptor" /> <interceptor name="multiselect" class="org.apache.struts2.interceptor.MultiselectInterceptor" /> <!-- Basic stack --> <interceptor-stack name="basicStack">[崔19] <interceptor-ref name="exception"/> <interceptor-ref name="servletConfig"/> <interceptor-ref name="prepare"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="actionMappingParams"/> <interceptor-ref name="params"> <param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</param> </interceptor-ref> <interceptor-ref name="conversionError"/> </interceptor-stack> <!-- Sample validation and workflow stack --> <interceptor-stack name="validationWorkflowStack">[崔20] <interceptor-ref name="basicStack"/>[崔21] <interceptor-ref name="validation"/> <interceptor-ref name="workflow"/> </interceptor-stack> <!-- Sample file upload stack --> <interceptor-stack name="fileUploadStack"> <interceptor-ref name="fileUpload"/> <interceptor-ref name="basicStack"/> </interceptor-stack> <!-- Sample model-driven stack --> <interceptor-stack name="modelDrivenStack"> <interceptor-ref name="modelDriven"/> <interceptor-ref name="basicStack"/> </interceptor-stack> <!-- Sample action chaining stack --> <interceptor-stack name="chainStack"> <interceptor-ref name="chain"/> <interceptor-ref name="basicStack"/> </interceptor-stack> <!-- Sample i18n stack --> <interceptor-stack name="i18nStack"> <interceptor-ref name="i18n"/> <interceptor-ref name="basicStack"/> </interceptor-stack> <interceptor-stack name="paramsPrepareParamsStack"> <interceptor-ref name="exception"/> <interceptor-ref name="alias"/> <interceptor-ref name="i18n"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="params"> <param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</param> </interceptor-ref> <interceptor-ref name="servletConfig"/> <interceptor-ref name="prepare"/> <interceptor-ref name="chain"/> <interceptor-ref name="modelDriven"/> <interceptor-ref name="fileUpload"/> <interceptor-ref name="staticParams"/> <interceptor-ref name="actionMappingParams"/> <interceptor-ref name="params"> <param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</param> </interceptor-ref> <interceptor-ref name="conversionError"/> <interceptor-ref name="validation"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="workflow"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> </interceptor-stack> <!-- A complete stack with all the common interceptors in place. Generally, this stack should be the one you use, though it may do more than you need. Also, the ordering can be switched around (ex: if you wish to have your servlet-related objects applied before prepare() is called, you'd need to move servletConfig interceptor up. This stack also excludes from the normal validation and workflow the method names input, back, and cancel. These typically are associated with requests that should not be validated. --> <interceptor-stack name="defaultStack">[崔22] <interceptor-ref name="exception"/> <interceptor-ref name="alias"/> <interceptor-ref name="servletConfig"/> <interceptor-ref name="i18n"/> <interceptor-ref name="prepare"/> <interceptor-ref name="chain"/> <interceptor-ref name="scopedModelDriven"/> <interceptor-ref name="modelDriven"/> <interceptor-ref name="fileUpload"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="staticParams"/> <interceptor-ref name="actionMappingParams"/> <interceptor-ref name="params"> <param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</param> </interceptor-ref> <interceptor-ref name="conversionError"/> <interceptor-ref name="validation"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="workflow"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="debugging"/> </interceptor-stack> <!-- The completeStack is here for backwards compatibility for applications that still refer to the defaultStack by the old name --> <interceptor-stack name="completeStack"> <interceptor-ref name="defaultStack"/> </interceptor-stack> <!-- Sample execute and wait stack. Note: execAndWait should always be the *last* interceptor. --> <interceptor-stack name="executeAndWaitStack"> <interceptor-ref name="execAndWait"> <param name="excludeMethods">input,back,cancel</param> </interceptor-ref> <interceptor-ref name="defaultStack"/> <interceptor-ref name="execAndWait"> <param name="excludeMethods">input,back,cancel</param> </interceptor-ref> </interceptor-stack> </interceptors> <default-interceptor-ref name="defaultStack"/>[崔23] <default-class-ref class="com.opensymphony.xwork2.ActionSupport" /> </package> |
3 自定义拦截器
我们也可以自定义拦截器,Struts2要求所有拦截器必须实现Interceptor接口。
Interceptor.java
public interface Interceptor extends Serializable { void destroy[崔24] (); void init[崔25] (); String intercept[崔26] (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()方法即可。
下面我们自定义一个拦截器
MyInterceptor
public class MyInterceptor extends AbstractInterceptor { @Override public String intercept(ActionInvocation invaction) throws Exception { System.out.println("MyInterceptor..."); return invaction.invoke(); } } |
4 注册拦截器
注册拦截器一共分为两步:
l 在<package>中声明拦截器;
l 在<action>中引用拦截器。
<package name="s8" namespace="/" extends="struts-default"> <interceptors> <interceptor name="MyInterceptor" class="cn.itcast.interceptor.MyInterceptor" /> </interceptors> [崔27] <action name="LoginAction"> <result>/index.jsp</result> <result name="input">/login.jsp</result> <interceptor-ref name="MyInterceptor" />[崔28] </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" />[崔29] <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" />[崔30] <interceptor-stack name="myStack"> <interceptor-ref name="defaultStack" /> <interceptor-ref name="MyInterceptor" /> </interceptor-stack> [崔31] </interceptors> <default-interceptor-ref name="myStack" />[崔32] <action name="LoginAction[崔33] "> <result>/index.jsp</result> <result name="input">/login.jsp</result> </action> </package> |
Struts2执行流程核心(了解)
综合练习之登录
1 搭建
1 完成页面设计
l /login.jsp(登录表单)
l /user/index.jsp(4个链接)
2 Action设计
LoginAction:如果用户名是itcast,并且密码是123,登录成功;
BookAction:4个请求处理方法,只做输出打印。
public class LoginAction extends ActionSupport implements ModelDriven<User> { private User user = new User(); @Override public String execute() throws Exception { if(user.getUsername().equals("itcast") && user.getPassword().equals("123")) { ActionContext.getContext().getSession().put("user", user); return SUCCESS; } else { this.addActionError[崔34] (this.getText("login.error")); return INPUT; } } @Override public User getModel() { return user; } } |
BookAction
public class BookAction extends ActionSupport { public String add() { System.out.println("add()..."); return NONE; } public String mod() { System.out.println("mod()..."); return NONE; } public String del() { System.out.println("del()..."); return NONE; } public String find() { System.out.println("find()..."); return NONE; } } |
3 添加国际化、输入校验
省略…
2 添加拦截器,对BookAction进行拦截
public class MyInterceptor extends AbstractInterceptor { @Override public String intercept(ActionInvocation invaction) throws Exception { User user = (User)ActionContext.getContext().getSession().get("user"); if(user == null) { ActionSupport action = (ActionSupport) invaction.getAction()[崔35] ; action.addActionError(action.getText("auth.error")); return "login"; } return invaction.invoke(); } } |
<package name="s8" namespace="/user" 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" /> <global-results> <result name="login">/login.jsp</result> </global-results> <action name="book_*" class="cn.itcast.action.BookAction" method="{1}"> <result>/index.jsp</result> <result name="input">/login.jsp</result> </action> </package> <package name="xx" namespace="/" extends="struts-default"> <action name="LoginAction" class="cn.itcast.action.LoginAction"> <result>/user/index.jsp</result> <result name="input">/login.jsp</result> </action> </package> |
[崔1]OGNL表达式
[崔2]OGNL表达式
[崔3]必须手动初始化
[崔4]当toType为String类型,说明是要把Person转换成String类型
[崔5]否则就是要把String转换成Person!这时value就是表单数据,但要注意,表单数据是String[]类型,我们要获取的是下标0的元素。
[崔6]当类型转换失败后,拦截器会跳转到input结束码,如果没有给出input结果码,那么就会报错。
[崔7]在input结果码对应页面中导入struts2标签库
[崔8]使用fieldError来打印字段错误信息
[崔9]把这一段copy到我们的校验文件中。
[崔10]注意,参数的名称要与set方法名称对应,而不是属性名称。因为set方法名称为setTrim(),所以参数名称为trim。
[崔11]校验文件的名称第2部分
[崔12]校验文件的第1部分
[崔13]使用name属性指定资源文件中的key值
[崔14]使用ActionSupport类的getText()方法获取资源文件信息。参数是资源文件中的key值。
[崔15]使用<message>的key属性指定资源文件中的key值
[崔16]这就是我们一直继承的包!!!
[崔17]因为我们定义的包继承了struts-default包,所以可以使用父包中定义的结果类型。
[崔18]这是struts-default包中定义的拦截器。注意,这只是定义的拦截器,不代理它们一定会执行。
[崔19]这是拦截器栈
[崔20]拦截器栈
[崔21]这个栈引用了上面的栈
[崔22]这是默认拦截器栈,每个Action执行之前都会执行这个栈中引用的拦截器。
[崔23]指定defaultStack为默认拦截器栈,这样才会在每个Action执行之前执行该栈中的拦截器。
[崔24]拦截器销毁之前被执行
[崔25]拦截器创建之后被执行
[崔26]每次拦截请求都会被执行。
[崔27]声明拦截器
[崔28]引用拦截器,这时在执行该Action时就会执行这个拦截器。
[崔29]defaultStack就不用在声明了,因为在struts-default包中已经声明过了。
[崔30]声明拦截器
[崔31]声明拦截器栈
[崔32]指定当前包的默认拦截器
[崔33]因为当前<action>没有指定拦截器,所以执行默认拦截器栈扣的拦截器。
[崔34]添加ActionError,可以在页面中使用<s:actionerror/>标签来打印它
[崔35]获取当前的Action对象