楼主有一年的实际工作经验,但是近期发现学的东西到多,但是总是感觉少了什么,最后发现,原来没有留下痕迹,整个人就觉得是学习的东西是漂浮的,没有在现实留下痕迹,也就说没有一种成就的快感,本人再次学习Struts2,方便以后在面试和学习中来总结原理和开发技巧,供大家分享。
Struts2
环境搭建
1.导入基础包
2.从源码获取struts.xml文件(如果没有dtd约束的,可以在核心包中struts2-core.jar中找到struts-2.3.dtd约束,本地导入)
3.web.xml配置StrutsPrepareAndExecuteFilter过滤器(过滤器设置拦截/*所有,但是struts2默认拦截.action和空路径)
执行过程
tomcat启动---》tomcat实例化并且初始化过滤器---》过滤器加载struts.xml文件(init方法可见)
浏览器发送请求---》tomcat获得请求,并且被过滤器拦截---》拦截截取动作名称,并从struts.xml中对应动作名称----》找到后根据配置文件中动作名称属性class实例化动作类(每次创建新的实例)---》执行对应的方法,返回字符串---》根据字符串匹配对应的结果视图跳转(也可以不跳转)---》响应给客户端
Struts2配置文件加载顺序
1.default.properties(不能修改,struts2-core\org\apache\struts2中,此xml文件有常量)
2.struts-default.xml(不能修改,struts2-core,此xml文件有拦截器和返回类型)
3.struts-plugin.xml(在struts2提供的插件jar包中)
4.struts.xml(可以修改)
5.struts.properties(可以修改,一般不用)
6.web.xml(可以修改)
后者会覆盖前者定义的属性(也就是说系统xml不能修改,但是用户xml可以重写常量,加载后覆盖系统xml定义的常量)
default.properties常量
struts.i18n.encoding=utf-8应用中使用的编码
struts.multipart.saveDir=
struts.multipart.maxSize=2097152文件上传总文件大小限制:2m
struts.action.extension=action,,能进入Struts2框架内部的url地址后缀名,多个用逗号分隔
struts.devMode=false是否是开发模式,为true时,改了配置文件,不需要重启服务器,开发阶段建议为true。
struts.enable.DynamicMethodInvocation=false是否允许动态方法调用(不安全,直接看到url调用的方法,但是开发速度快)
struts.ui.theme=xhtml页面展示用的主题
使用<constant>来配置xml文件的常量
package元素
name属性:指定包名称,包名称唯一
extends属性:指定当前包的父包
abstract属性:把包声明为一个抽象包,抽象包就是用来被继承的。只有没有action元素的包,才能被定义抽象包
namespace属性:名称空间,当指定了名称控件之后,访问路径就变成:
访问路径=名称控件/动作名称(注意,不指定该属性时候,默认值是"",不是"/")
名称空间的搜索顺序:
第一步:先去找对应的名称空间
在指定的名称控件下找到了:就执行第二步
在指定的名称控件下没有找到:按照名称控件结构向上追溯,一直到根(带/)名称空间,只要任何一级找到就执行第二部。
第二步:
找到动作名称就执行动作方法
没有找到,就去默认的(namespace=" " 或者不写)名称空间找
动作类的三种定义方式
接口action(里面有常用常量)
success 执行成功,前往指定位置
none 不返回任何结果视图,和return null一样
error 当执行动作方法,出现异常,前往指定位置
input 数据回显
login 一般用于返回登录页面
继承ActionSupport类(实现了action,和表单验证,国际化,和序列化)
当类中一个动作方法都不提供时,有一个默认的动作方法(父类中):execute()
也就是说不用写method属性,默认走execute,返回success
如果连action中class属性都不写,还是会走execute,返回success,源码可知,package继承了struts-default,而在struts--default.xml文件可知,默认class引用就是ActionSupport
总结:1.三种方式,什么都不继承和实现接口,实现action,继承ActionSupport类
2.继承ActionSupport类,不写mothed属性,默认走success()方法
3.不写class属性时候,根据继承的struts-default找到默认的class引用正是actionSupport,所以还是会执行success()方法,返回success
通配符使用
中级
<action name="user_*" class="com.mmm.action.userAction" method="{1}">
<result name="success">/{1}.jsp</result>
</action>
高级
<action name="*_*" class="com.mmm.action.{1}Action" method="{2}">
<result name="success">/{2}.jsp</result>
</action>
动态方法调用
需要在xml配置struts.enable.DynamicMethodInvocation=true才可以使用
动作名称!动作方法(不需要定义method属性)
好处是定义action后直接调用,不需要匹配method
坏处是类的方法暴露在url上,危险(不过看到前辈用在新闻大屏的轮播上也是很舒服的)
结果视图配置
result元素:
Type属性源码
<result-types>
<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>
自定义结果视图(返回验证码,字符串,json都可以)重点
步骤:继承StrutsResultSupport,重写doExecute()方法,如果在result中使用自定义的result,需要自定义结果类型<result-type name="" class=""></result-type>
public class CAPTCHAResult extends StrutsResultSupport {
//通过配置文件,调整生成图片的大小
private int width;
private int height;
protected void doExecute(String finalLocation, ActionInvocation invocation)
throws Exception {
/*
* 使用第三方生成验证码的jar包
* 1.拷贝ValidateCode.jar到工程lib目录
* 2.创建ValidateCode的对象
* 3.获取响应对象输出流
* 4.输出到浏览器
*/
//创建ValidateCode的对象
//参数详解:1:图像宽度 2.图像高度 3.数字的格式 4.干扰线条数
ValidateCode code = new ValidateCode(width,height,4,10);
//获取响应对象
HttpServletResponse response = ServletActionContext.getResponse();
//输出到浏览器
code.write(response.getOutputStream());
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
题外话
:
json作为常用返回类型,我们要使用struts2对json扩展,需要导入struts2-json-plugin.jar插件包,看插件的xml,发现也是自定义了返回值类型
<package name="json-default" extends="struts-default">//开发中不需要继承struts-default了,只需要继承json-default即可
<result-types>//自定义了结果视图
<result-type name="json" class="org.apache.struts2.json.JSONResult"/>
</result-types>
<interceptors>//以下是拦截器来获取数据用的
<interceptor name="json" class="org.apache.struts2.json.JSONInterceptor"/>
<interceptor name="jsonValidation" class="org.apache.struts2.json.JSONValidationInterceptor"/>
<!-- Sample JSON validation stack -->
<interceptor-stack name="jsonValidationWorkflowStack">
<interceptor-ref name="basicStack"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel</param>
</interceptor-ref>
<interceptor-ref name="jsonValidation"/>
<interceptor-ref name="workflow"/>
</interceptor-stack>
</interceptors>
</package>
JSONResult类可知,如果想使用json类型,需要注入root参数(root代表想序列化的类型),struts2会在加载类的时候动态注入root属性,然后通过拦截器找到该属性进行json解析(也可以说,只要类中的属性,都可以使用param去定义值注入进去)
<param name="root">值</param>
全局结果视图
顾名思义就是所有继承的空间名称的动作名称都可以访问到结果视图
如果动作类中有重名的结果视图,求近舍远,如果没有则找远的,在没有就报错,哦了
<global-results>
<result name="success">```````
</global-results>
获取ServletAPI(重要)
第一种:ServletActionContext对象获取request,respone(注意细节,其他的都是tomcat的类型,唯独返回的request是个struts的包装类型,el表达式能获取action类的属性就是因为次包装类型改写了request中的getAttribute方法,如果找不到request的域属性,则会在action的valueStack中找,也就是属性上找)
第二种:实现ServletRequestAware,ServletResponseAware,ServletContextAware,并且定义对应属性
实现setXXX方法,并且赋值对应属性,通过拦截器会自动的调用action的setXXX来注入进来
具体拦截器名称为:ServletConfigInterceptor(源码看出,通过拦截器拦截后,给action判断是否属于某个实现接口来动态调用方法注入值)
public String intercept(ActionInvocation invocation)//当前的action代理类
throws Exception
{
HttpServletRequest request;
Object action = invocation.getAction();
ActionContext context = invocation.getInvocationContext();
if (action instanceof ServletRequestAware) {//如果实现了、ServletRequestAware接口
request = (HttpServletRequest)context.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest");
((ServletRequestAware)action).setServletRequest(request);//设置值,而子类重写了此方法,所以调用的是子类action的方法!!!
}
if (action instanceof ServletResponseAware) {
HttpServletResponse response = (HttpServletResponse)context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse");
((ServletResponseAware)action).setServletResponse(response);
}
if (action instanceof ParameterAware) {
((ParameterAware)action).setParameters(context.getParameters());
}
if (action instanceof ApplicationAware) {
((ApplicationAware)action).setApplication(context.getApplication());
}
if (action instanceof SessionAware) {
((SessionAware)action).setSession(context.getSession());
}
if (action instanceof RequestAware) {
((RequestAware)action).setRequest((Map)context.get("request"));
}
if (action instanceof PrincipalAware) {
request = (HttpServletRequest)context.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest");
if (request != null)
{
((PrincipalAware)action).setPrincipalProxy(new ServletPrincipalProxy(request));
}
}
if (action instanceof ServletContextAware) {
ServletContext servletContext = (ServletContext)context.get("com.opensymphony.xwork2.dispatcher.ServletContext");
((ServletContextAware)action).setServletContext(servletContext);
}
return invocation.invoke();
}
封装请求正文到对象中(非常重要)
1.静态参数封装(使用注入的方式,给动作类中的参数赋值,写死,一般用于系统参数的配置中使用,用户动作类一般使用动态参数封装)
在配置文件中<param name="username"></param>,通过staticParams拦截器来注入给动作类(可以看出拦截器起到至关重要的作用)
2.动态参数封装
表单中的name和动作类的属性一致,也是通过params拦截器给动作类的参数注入值(从拦截器的加载顺序来看,相同的参数名称,动态会覆盖静态)
在对象参数封装中,我们直接写private User user;不需要实例化对象,就可以调用user而不报错,给user对象的get和set方法上分别写上输出语句可知
getUser,setUser,getUser,值
(getUser)首先会调用getUser判断是否为空,如果为空,就利用反射动态创建一个User
(setUser)然后把值设置给User对象里面
(getUser)某个方法调用了User然后就返回值。
可以看出不需要实例化对象一样可以,struts2对实例化这块改善了很多
3.模型驱动封装动参数(一般用于base系列,开发中采用的方式)
实现ModelDriven接口的并且给对应封装数据模型的泛型,实现getModel()方法,该方法返回的是数据模型(注意:数据模型必须我们来实例化)
此驱动模型通过ModelDrivenInterceptor拦截器实现,通过源码
public String intercept(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction();
if(action instanceof ModelDriven) {//action是否实现ModelDriven接口
ModelDriven modelDriven = (ModelDriven)action;
ValueStack stack = invocation.getStack();
Object model = modelDriven.getModel();
if(model != null) {
stack.push(model);//把对象给压栈进去,所以说可以不用写对象.属性,而直接写属性的原因!!!
}
if(this.refreshModelBeforeResult) {
invocation.addPreResultListener(new ModelDrivenInterceptor.RefreshModelBeforeResult(modelDriven, model));
}
}
return invocation.invoke();
}
类型转换器(了解,实际开发几乎不用)
实际开发中用户通过浏览器输入的数据都是String或者是String[]
Struts2提供常用类型转换
a.基本数据类型自动转换
b.日期类型:默认按照本地日期格式转换(yyyy-MM-dd)
c.字符串数组:默认用逗号+空格,连接成一个字符串
自定义类型转换器(开发中不建议使用)
TypeConverter接口中实现接口过长所以不用,看子类
DefaultTypeConverter的converValue()方法,只有三个参数,但是又有一个类继承
StrutsTypeConverter的convertFromString()和convertToString()方法,实现这两个抽象方法
局部类型转换器:声明方式是以使用属性名称作为key,以类型转换器的全类名作为value(文件名称:javabean的名称-conversion.properties)
全局类型转换器:声明方式是以使用数据数据作为key,以类型转换器的全类名作为value(文件名称:xwork-conversion.properties)
推荐使用全局类型转换器
Struts2的标签和错误处理和中文显示
在表单提交后,发生错误需要回显错误,并且打印错误信息(使用struts的标签,虽然和原生类型一样,但是有很多好处,表单回显,数据交互等)
使用struts2标签(requiredLabe:必须填写标签,符号*,requiredPosition="left"左边生成*,如果想回显密码:在password标签属性showpassword)
<s:fielderror/>字段错误
<s:actionerror>动作错误
(国际化)虽然错误回显了,但是错误提示是英文
在bean类型下创建bean名称.properties
invalid.fieldvalue.birthday="请输入正确的日期格式"
Struts2编程式验证和声明式验证
一般前台用js验证数据,但是如果js被恶意删除,就要考虑后台验证
编程式验证(硬编码)
实现后台验证的前提是继承ActionSupport类重写validate方法
public void validate() {//会在动作类执行前,动作类属性
if(StringUtils.isEmpty(user.getName())) {
//存入错误信息,直接调用父类的addFidldError方法,存入错误信息,第一个参数是name表单属性,第二个是错误信息
addFieldError("user.name","请输入用户名");
}
}
但是发现,如果实现validate()方法后,所有的动作类的动作方法全部都会被验证,如果不想都被拦截有两种方式:
1.如果不想验证的方法就加上@SkipValidation
2.定义验证方法的名称:validate+动作方法名称(动作方法还需要首字母还需要大写)
声明式动作类验证
通过编写验证规则的xml文件。需要验证时,编写xml文件
步骤:1.针对动作类所有的动作进行验证,在动作类所在的包中,建立一个ActionClassName-validation.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.dtd">
<validators>
<!-- field中name属性指定 -->
<field name="name">
<!-- struts2框架为我们集成了很多内置验证器 xwork-core-2.3.15.3.jar\com\opensymphony\xwork2\validator\validators-->
<field-validator type="requiredstring">
<message>用户名呢?</message>
</field-validator>
</field>
<!-- 基于验证器验证 -->
<validator type="requiredstring">
<!-- 注入的方式提供要验证的字段信息 -->
<param name="fieldName">pwd</param>
<message>密碼必須輸入</message>
</validator>
</validators>
也是拦截全部的方法,如果不想全部拦截,在不拦截的方法上加@SkipValidation
声明式动作类动作方法验证(推荐)
步骤:1.针对动作类中某个方法进行验证,在动作类所在的包中,建立一个ActionClassName-方法名-validation.xml文件