Struts2
简介
由WebWork发展而来,也属于MVC框架,与Struts1有很大的区别。
优点
1、不依赖与Servlet API,对比Struts1的execute方法的参数HttpServletRequest和HttpServletResponse
2、提供了拦截器,便于AOP编程
3、提供了类型转换器,可以把特殊请求的参数转换成需要的类型
4、支持多种视图技术,如:jsp、freemarker、velocity等
5、提供了输入校验,细分到方法的级别
6、提供了i18n资源文件管理,可以细分到全局范围、包范围和action范围
搭建环境
1、添加相关的jar文件
struts2-core-2.x.x.jar:struts2的核心类库
xwork-2.x.x.jar:xwork类库
ognl-2.6.x.jar:OGNL表达式
freemarker-2.3.x.jar:UI标签的模板
commons-logging-1.1.x.jar:日志包
commons-fileupload-1.2.1.jar:文件上传组件
2、编写struts.xml配置文件
3、在web.xml配置struts2启动
Struts2通过过滤器标签启动的
struts配置:
struts以包的形式管理action,包必须有一个name属性(包的名称在配置文件中必须唯一),可以有一个namespace(是action访问路径的一部分),访问时应该是namespace/action的name.action形式进行,每个包通常都应该继承struts-default包(该包提供了大量的拦截器和result类型,完成struts2的框架功能,在struts2-core-2.x.x.jar中的struts-default.xml中定义的),如果一个包有abstract="true"属性,则该包中不能包含action定义,只能被其他包继承。
action中name指定了名称,class指定了类型,method指定了访问action类的方法(方法签名是:public String XX();),在页面中可以直接访问类中定义的getXXX方法(使用反射去调用的)
action的默认值:
没有定义action的class属性,则默认为ActionSupport
没有定义action的method属性,则默认为execute方法
没有定义result的name属性,则默认为success
result视图转发类型:
dispatcher(默认的):请求转发
redirect(重定向到Jsp):
redirectAction(重定向到Action):如果重定向到不同包的action,则需要如下:
<resulttype="redirectAction">
<paramname="namespace">/命名空间名称</param>
<paramname="actionName">action名称</param>
</result>
plainText(显示原始文件内容):常用于查看源代码
<resulttype="plainText">/xxx.jsp</result>
也可以按如下设置:
<result type="plainText">
<paramname="location">/xxx.jsp</param>
<paramname="charSet">UTF-8</param> <!-- 指定读取文件的编码 -->
</result>
可以在包内使用<global-results>定义包内的全局视图,供该包内的action使用
可以定义一个抽象包,其中包含<global-results>,供其他包继承,从而达到所有包内的action都可以访问的全局视图
可以在action标签内,使用param子标签给action类的属性注入值,如:
<actionname="xx" class="yy">
<param name="属性名">属性值</param>
</action>
可以在struts.xml配置文件中,使用<constantname="struts.action.extension" value="do,action"/>形式,改变请求路径的后缀为do或action等,注意:要求web.xml中<url-pattern>*</url-pattern>。
加载常量的顺序:
struts-default.xml--> struts-plugin.xml --> struts.xml --> struts.properties -->web.xml
后面定义的重名常量会覆盖前面的
struts.i18n.encoding常量:指定默认的编码集
struts.serve.static.browserCache常量:设置浏览器是否缓存
struts.configuration.xml.reload常量:设置系统是否自动加载修改后的配置文件
struts.devMode常量:是否开发模式
struts.objectFactory常量:与spring集成时,确定由谁来创建action对象
struts.multipart.maxSize常量:上传文件总大小的限制
struts2的处理流程:
1、用户请求 --> 如果请求后缀是action,则跳转到struts2的框架中,否则不处理
2、请求发送至StrutsPrepareAndExecuteFilter类
3、经过一系列的拦截器(包含用户自定义的拦截器),这是struts2的核心部分
4、送至action进行处理(struts1.x的action是单例模式,struts2的action是原型模式[每次都会创建一个新的action,是线程安全的])
5、把处理结果和action的result进行对比
6、跳转到相应的视图或action
可以把struts配置文件拆分成多个(一般以模块进行划分),然后struts配置文件中再使用include标签将其组合起来,以达到给配置文件减肥,方便管理的目的。
struts2中action的动态方法调用:
1、可以使用【action!方法名.action】的方式(不推荐),如:
原来:http://localhost:8080/项目名/命名空间/action名称.action --> 调用action名称对应的方法
动态:http://localhost:8080/项目名/命名空间/action名称!新方法名.action --> 调用action类中的新方法
可以使用struts.enable.DynamicMethodInvocation常量设置是否支持动态方法调用
2、也可以通过通配符(*)定义action(推荐),如:
<action name="xxx_*"class="yy" method={1}>
...
</action>
其中在name属性中定义了action的名字,其中含有通配符,在method中可以依据下标(从1开始)得到对应的通配符所表示的内容。
{1}的形式也可以在class属性值以及result属性值中使用,例如:
动态:http://localhost:8080/项目名/命名空间/xxx_新方法名.action --> 调用action类中的新方法
在struts2中接收请求参数:
struts2通过在action类中定义一个与请求参数同名的属性,并提供setter方法,然后通过反射来得到请求参数。
也可以说接收复合类型的参数,不过需要使用.进行分隔,如:person.id、person.name等
struts2中有2中类型转换器:(局部[对某个action起作用]和全局)
自定义类型转换器必须继承DefaultTypeConverter类,重写其convertValue方法即可(双向转换)。
局部的类型转换器:
在action类所在的包中添加一个【action名称-conversion.properties】文件,并在其中声明属性和类型转换器的映射,即可以将用户自定义的类型转换器注册成一个局部的类型转换器。例如:
public class DateConverter extendsDefaultTypeConverter{
privateSimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
//重写父类的方法(实现了双向转换)
publicObject convertValue(Map context,Object value,Class toType){
try{
//把字符串转换成指定的类型
if(toType== Date.class){
String[] params = (String[])value; // 必须把请求参数转成字符串数组(因为请求参数可能是一组多选框的值)
return sdf.parse(params[0]);
}
//把指定的类型转成字符串
elseif(toType == String.class){
Date date = (Date)value;
return sdf.format(date);
}
}catch{
}
return null;
}
}
在XXXaction的包内的XXXaction-conversion.properties文件中:
#指定XXXaction的birthday属性使用的转换器
birthday=com.phome.converter.DateConverter
全局类型转换器:
在src目录下添加一个【xwork-conversion.properties】文件,在其中编写内容如下:
#数据类型 = 转换器类型
java.util.Date=com.phome.converter.DateConverter
访问Application、session和request对象
1、得到模拟对象
ActionContext ctx =ActionContext.getContext();
ctx.getApplication() --> 得到表示application的map集合
ctx.getSession() -->得到表示session的map集合
ctx --> 得到表示request的map集合
2、得到对象
2.1、通过ServletActionContext对象直接得到
ServletContextapplication = ServletActionContext.getServletContext();
HttpSessionsession = ServletActionContext.getRequest().getSession();
HttpServletRequestreq = ServletActionContext.getRequest();
HttpServletResponseres = ServletActionContext.getResponse();
2.2、通过实现ServletRequestAware、ServletResponseAware、ServletContextAware接口,由struts2在运行时注入
文件上传:
1、需要导入commons-uploadfile、commons.io包
2、在form表单中使用post方法,并且声明enctype="multipart/format-data"属性
3、定义的属性
private File image; // 表示上传的文件
private String imageFileName; // 表示上传文件的名称(约定大于规范,必须这样写)
// setter和getter方法
在action的方法中,把临时目录中的文件进行保存
String realPath =ServletActionContext.getServletContext().getRealPath("/images");
if(image != null){
FilesaveFile = new File(new File(realPath),imageFileName);
if(!saveFile.getParentFile().exists()){
saveFile.getParentFile().mkdir();
}
FileUtils.copyFile(image,saveFile);// 使用commons.io中的工具类完成拷贝
}
4、如果上传多个文件,客户端文件名称相同,服务器端则使用数组或List集合进行,如:
private File[] images;
private String[] imagesFileName;
其他步骤同上
自定义拦截器:(实质是AOP的Around通知)
要实现Interceptor接口的intercept方法即可。
应用:
1、在包中说明定义拦截器
2、定义拦截器栈(必须包含系统定义的拦截器栈,必须在最前面,然后加上自己定义的拦截器)
3、在action中引用自己定义的拦截器栈
例如:
<interceptors>
<!-- 定义自己的拦截器 -->
<interceptorname="permission"class="com.phome.interceptors.Permission"/>
<!-- 定义拦截器栈(包含了系统定义的,并且是第一个) -->
<interceptor-stackname="permissionStack">
<interceptor-refname="defaultStack"/>
<interceptor-refname="permission"/>
</interceptor>
</interceptors>
...
<!--在action中引用自定义的拦截器栈 -->
<action...>
<interceptor-refname="permissionStack"/>
</action>
如果对包中的所有action都要进行拦截,在需要在包中定义引用的拦截器栈
例如:
<!-- 包级别的拦截器 -->
<default-interceptor-refname="permissionStack"/>
每个包只能指定一个默认的拦截器,每个action可以指定不同的拦截器,action的拦截器级别高。
输入校验:
1、手动编写代码
重写ActionSupport的validate方法即可完成对所有方法的输入验证,使用this.addFieldError("错误信息的关键字","错误信息")把错误信息添加到FieldErrors集合中,然后使用struts2的标签(<s:fielderror/>)在页面中显示错误信息。
如果只校验某些方法,则使用validateXXX(其中XXX是方法名),这是“约定大于规范”的一个体现。
例如:
publicvoid validateUpdate(){
if(this.userName == null ||this.userName.trim().equals("")){
this.addFieldError("userNameNull","用户姓名不能为空!");
}
if(this.mobile == null ||this.mobile.trim().equals("")){
this.addFieldError("mobileNull","手机号码不能为空!");
}elseif(!Pattern.compile("^1[358]\\d{9}$").matcher(this.mobile).matches()){
this.addFieldError("mobileFormatError","手机号码格式错误!");
}
}
在配置中提供input关键字对应的视图
校验的流程:
1.1、类型转换器对请求参数进行类型转换,并把转换后的值赋给action中的属性,
1.2、如果转换过程中发生异常,则conversionError拦截器会把异常信息封装到fieldErrors中,进入下一步,
1.3、struts2会使用反射技术先调用validateXXX方法,最后才调用validate方法,
1.4、如果发现fieldErrors.size() > 0(有出错信息),在转发至input关键字对应的视图,没有错误,则进行action中的处理方法。
注意:
类型转换失败或校验失败均会进入到input关键字对应的视图中,注意区分。
2、使用xml配置
需要继承ActionSupport类,需要提供一个校验文件【action类名-validation.xml】,该校验文件必须和action类放在同一个包中。例如:
<validators>
<!-- 需要校验的字段 -->
<field name="userName">
<!--指定一个系统已存在的校验器-->
<field-validatortype="requiredstring">
<!-- 为校验器注入一个属性值 -->
<param name="trim">true</param>
<!-- 当校验失败时的提示信息 -->
<message>用户姓名不能为空!</message>
</field-validator>
</field>
<field name="mobile">
<field-validatortype="requiredstring">
<message>手机号码不能为空!</message>
</field-validator>
<!--指定一个系统已存在的正则表达式校验器 -->
<field-validatortype="regex">
<paramname="expression"><![CDATA[^1[358]\d{9}$]]></param>
<message>手机号码格式错误!</message>
</field-validator>
</field>
</validators>
系统提供的校验器需要参考com.opensymphpny.xwork2.validator.validators.default.xml文件。
如果想对action中的指定方法进行校验,需要提供一个校验文件【action类名-action类在struts.xml配置中的名称-validation.xml】,文件内容同上。
例如:
在配置文件中:
<actionname="user_*" class="com.phome.UserAction"method="{1}">
...
</action>
分析:
请求add()方法时,url为/person/user_add.action
namespace = /person
action名称 = user_add
action类名 = UserAction
所以,add方法的校验文件命名应该是:UserAction-user_add-validation.xml
如果存在所过方法的校验和某个方法的校验,则struts2会进行校验要求的合并,如果发生冲突,则使用后面文件的校验规则覆盖前面的。
如果存在action类的继承,则搜索校验文件的顺序是:父类的所有方法校验文件 --> 父类某个方法的校验文件 --> 子类的所有方法校验文件 --> 子类某个方法的校验文件,然后进行汇总校验。
struts2的国际化:(全局范围、包范围、action范围)
1、全局范围的资源文件
中文:资源文件的基名_zh_CN.properties
英文:资源文件的基名_en_US.properties
在struts2的配置中,添加<constant name="struts.custom.i18n.resource"value="资源文件的基名"/>即可
然后就可以使用资源文件中的key进行访问:
在jsp页面中,使用<s:text name="键"/>进行访问
在action类中,继承ActionSupport类并使用其getText("键")进行访问
在表单标签中,使用<s:textfield key="键" .../>进行访问
在资源文件中可以包含占位符(下标从0开始),如:{0}
在jsp页面中,使用如下格式进行访问
<s:text name="键"/>
<s:param>第一个参数</s:param>
<s:param>第二个参数</s:param>
</s:text>
在action类中,继承ActionSupport类并使用其getText("键",new String[]{"参数1","参数2"})进行访问
2、包范围的资源文件
在包中,必须以package_en_US.properties或package_zh_CN.properties方式命名,包及其子包都可以访问到该资源文件,如果查找一个key,则先到包范围内找,找不到才到全局范围中找
3、action范围的资源文件
针对某个action的专用资源文件,在action所在的包中,必须以action类名_en_US.properties或action类名_zh_CN.properties方式命名,如果查找一个key,则先到action范围内找,找不到再到包范围内找,还找不到才到全局范围中找
可以通过<s:i18n>标签直接访问资源文件,而不需要任何配置
例如:
<s:i18nname="资源文件的基名">
<s:text name="键"/>
</s:i18n>
或
<s:i18nname="路径/package"/>
<s:text name="键1"/>
<s:text name="键2">
<param>参数</param>
</s:text>
</s:i18n>
或
<s:i18nname="路径/action类名"/>
<s:text name="键1"/>
<s:text name="键2">
<param>参数</param>
</s:text>
</s:i18n>
OGNL(Object Graphic Navigation Language):对象图导航语言。
OGNL的特点:
1、支持对象方法的调用,如:xxx.sayHello()
2、支持类静态方法的调用【 @类的完全限定名@方法名(参数)】,如:@java.lang.String@format('xxx%s','yyy')
3、操作集合对象
OGNL必须要和struts2的标签库的标签配合使用。
OGNL上下文中包含了ValueStack、parameters、request、session、application、attr等对象,其中ValueStack是根对象,其中的对象可以直接访问,不需要使用#,ValueStack之外的容器中的对象访问时,需要使用#进行,每次一个请求,struts2都会创建一个新的ActionContext、ValueStack和action,然后把action放入ValueStack中,所以,在ValueStack中,可以直接访问action中的属性。
访问OGNL根对象(ValueStack)中的对象的属性,除了使用OGNL外,还可以使用EL表达式直接访问。
创建list对象:
<s:set id="list"value="{'x','y','z'}" />
<!-- list对象和ValueStack等对象属于同一个级别,但需要使用#访问 -->
<s:iteratorvalue="#list">
<!--iterator在迭代集合时,会把当前迭代的对象放到ValueStack的栈顶,所以s:property不需要写value -->
<s:property/>
</s:iterator>
创建map对象:
<s:set id="map"value="#{'key1':'value1','key2':90,'key3':'value3'}" />
<s:iterator value="#map">
<!--StackValue的栈顶是当前迭代对象entry,entry对象有key和value属性 -->
<s:propertyvalue="key"/>=<s:property value="value"/>
</s:iterator>
投影产生子集合:
集合对象.{子集合的描述}
例如:
books.{?#this.price> 60} // 找出价格大于60的图书的子集合
其中:
?表示条件
#this表示要进行迭代的books集合中的当前对象
Struts2标签:
1、<s:property>:用于输出指定值。
default:如果输入为null,则使用该缺省值
escape:是否格式化HTML代码
value:要输出的属性值或OGNL表达式,如果无该属性,默认输出ValueStack栈顶的值
2、<s:iterator>:用于迭代
value:被迭代的集合,如果无该属性,默认输出ValueStack栈顶的值
status:迭代时,附加属性
intgetCount()、intgetIndex()、booleanisEven()、booleanisOdd()、booleanisFirst()、booleanisLast()
例如:
<s:iteratorvalue="#list" status="st">
<font color=<s:iftest="#st.odd">red</s:if><s:else>blue</s:else>>
<s:property/>
</font><br/>
</s:iterator>
3、<s:if test="OGNL表达式">、<s:else>、<s:elseif test="OGNL表达式">
例如:
<s:set name="age"value="25" />
<s:if test="#age == 25">
年龄是25
</s:if>
<s:else>
年龄不是25
</s:else>
如果<s:set name="age"value="25" scope="request"/>,则访问时,必须是#request.age
4、<s:url>:生成url,可以添加一个<s:param>参数标签
action:在struts.xml在action的名字
namespace:action所在的命名空间
例如:
<s:url action="abc_add"namespace="/test">
<s:paramname="id" value="12"/>
</s:url>
生成的url为:项目名/test/abc_add.action?id=12
%:当属性为字符串,而且是需要计算时,使用%
<s:set name="myurl"value="http://www.sina.com.cn"/>
<s:url value="#myurl"/><br/> --> 输出#myurl
<s:url value="%{#myurl}"/><br/> --> 输出http://www.sina.com.cn
5、复选框组标签:
list集合:<s:checkboxlistname="list" list="{'aa','bb','cc'}"value="{'aa'}"/>
map集合:<s:checkboxlistname="map" list="{1:'aa',2:'bb',3:'cc'}"listKey="key" listValue="value"value="{2,3}"/>
6、默认主题:
在struts.xml在添加如下节点:
<constantname="struts.ui.theme" value="simple"/>后,则可以去掉struts标签生成的多余HTML标签
7、单选框:
对象:<s:radio name="list"list="#request.persons" listKey="id"listValue="name"/>
list集合:<s:radio name="list"list="{'aa','bb','cc'}" value="aa"/>
map集合:<s:radio name="map"list="{1:'aa',2:'bb',3:'cc'}" listKey="key"listValue="value" value="2"/>
8、使用<s:token/>防止表单重复提交
1、在表单中加入<s:token/>
2、在action配置中,引用系统提供的拦截器token,并添加<resultname="invalid.token"/>/输入页面</result>,注意不要忘了引用系统默认的拦截器