struts2细节汇总
本文总结一些struts2的细节问题
关于struts2验证框架的流程
struts2里的校验,实际上是由几个拦截器,和几个接口共同完成的
如果Action继承自ActionSupport类,那么就实现了Validateable接口和ValidationAware接口,提供了validate()方法、hasError()方法,以及一组添加错误信息的方法(最常用的是addFieldError()方法)
默认包的拦截器栈有一部分是这样的:
1 | params --> conversionError --> validation --> workflow |
首先params拦截器和conversionError拦截器先起作用,把HTTP请求参数放入ValueStack中,如果有错误,会调用ValidationAware接口的添加错误的方法(其中一个是addFieldError)
然后到了validation拦截器,它会执行验证框架,如果有错误,也调用ValidationAware上的方法
最后到了workflow拦截器,第一个阶段它会判断Action是否实现了Validateable接口,如果是的话,则调用validate()方法,如果有错误,就调用ValidationAware的方法。然后第二阶段,它会调用hasError()方法,看看是否有错误,如果有的话,返回INPUT
所以,实际上可以同时使用struts2的校验框架,和Action上的validate()方法。对于workflow拦截器第二阶段的工作(检查错误)来说,它并不清楚,错误来自于哪里,是来自于校验框架,还是来自validate()方法,对workflow拦截器来说没有区别,只要有错误,就返回INPUT
关于i18n
使用国际化资源文件,标准的做法是
1 | <s:text name="homepage.greeting" /> |
这里假设资源文件里有一个key是homepage.greeting 如果不想用s:text标签的话,应该怎么办呢?
实际上,s:text标签调用的是TextProvider接口的getText()方法
而ActionSupport实现了TextProvider接口,所以如果Action是继承自ActionSupport,那么它也就实现了TextProvider接口。同时,Action对象会被放入值栈,所以用OGNL表达式,是可以直接取到资源文件中的国际化文本的,方法是
1 | ${getText("homepage.greeting")} |
以上这句OGNL表达式,效果相当于
1 | <s:text name="homepage.greeting" /> |
struts2插件加载体现的一种设计思路
struts2加载配置是遵循如下顺序:
1 | struts-default.xml -> struts-plugin.xml -> struts.xml |
其中,struts-default是框架默认提供的,struts.plugin是插件提供的,struts是用户自定义的
struts2框架启动时,会收集以上所有配置文件,然后进行聚合汇总,得到一个总体的配置信息
这种思路很常见,比如MAVEN,也是首先提供了一个超级POM,和项目自定义POM进行聚合。这种设计思路,是值得借鉴的
ResultType
struts2定义了多种resultType,包括dispatcher和redirectAction等
在struts-default.xml文件中,定义了所有的resultType类型
1 | <result-types> |
有空可以跟进去看看源码,这里重点说的是默认的dispatcher和redirectAction的区别
这里点击“删除”,会执行一段javascript代码:
1 | $(document).ready(function() { |
关键就是会跳转到delete.action?id=xxx这个URL,进入Action的delete()方法
1 | @Autowired |
此时struts.xml的配置是这样的:
1 | <package name="bookManage" extends="struts-default" namespace="/book"> |
代码很简单,就不解释了。功能可以实现,不过有个问题,就是用这种配置方式,删除操作之后,URL是这样的:
这时用户如果用F5刷新浏览器,就会发生重复提交的问题。所以可以这样修改,将resultType改成redirectAction
1 | <package name="bookManage" extends="struts-default" namespace="/book"> |
这样执行完delete.action后,会跳转进list.action,所以delete()方法也可以删除一行代码
1 | @Autowired |
实际看点击“删除”按钮的前后URL对比
实现了我们的目的。总结来说,默认的resultType是dispatcher,一般用来处理jsp跳转。如果希望一个action执行之后进入另一个action,可以用redirectAction,如果要多个action共同响应一个请求,可以用chain
最后补充一点,通过redirectAction进入新的Action,会新创建一个Action的实例
1 | public String list() { |
1 | <action name="delete" class="bookAction" method="delete"> |
输出结果:
1 | after redirectAction, currentBook is null or not |
OGNL相关技巧
OGNL有一些比较冷门的用法
在Result中使用OGNL表达式
实际上除了在jsp里可以使用OGNL表达式之外,在Result的配置里也是支持的,这点在RedirectAction中尤其好用
1 | <result type="redirectAction"> |
上面的param1和param2会成为请求的参数,其中param1是硬编码的,而param2是从ValueStack中取出的值
在properties文件中使用OGNL表达式
比如在resource.properties中,有一个greeting.word = hello ${user.name}
然后在jsp页面使用标签
1 | <s:text name="greeting.word" /> |
OGNL会创建实例
如果Action里有一个字段user,然后jsp里提交user.name,则user的name字段会被自动赋值,但是实际上,user字段没有初始化过,为什么不会NPE呢
这是OGNL在幕后起的作用,user.name是一个OGNL表达式,当OGNL解析器在属性链上发现一个为NULL的属性时,它会尝试创建一个实例并赋值
对于开发者来说,只需要给这个类一个无参构造方法,并为此字段提供一个setter方法即可
OGNL字面量
OGNL表达式还可以用来直接创建List和Map
1 | {1,2,3} |
这就创建了一个List
1 | #{"key1":"value1","key2":"value2"} |
类似的,这就创建了一个Map
这种语法一般是用在jsp页面里
OGNL操作符
OGNL还可以使用操作符
比如
1 | ${user.age + 1} |
OGNL方法调用
1 | <s:if test="page.hasNext()"> |
OGNL调用静态方法和字段
1 | @com.huawei.test.Utils@someStaticMethod() |
不过我认为这种写法是应该尽量避免的
好用的struts2-config-browser-plugin插件
放到WEB-INF/lib下,就安装了这个插件,十分简单
之后访问
1 | http://ip:port/app/config-browser/index.action |
就可以打开当前struts2环境的信息页面