主要说了些拦截器相关的内容,还有大量的 OGNL 相关的东西,最后补充了个注解,不过看到有人说 sturt2 几乎现在很少用了,大多数公司都用 springMVC ,还得抓紧时间继续学呐
还有一个模型驱动拦截器放在了笔记一中,因为这篇已经太长了….
注入对象
Action 既然只是普通的一个 java 类,如果想要获得 request 等内的数据应该怎么做的,这时候就用到反射技术来注入对象了;具体的注入实现是 ServletConfigInterceptor 这个拦截器,支持注入那些对象可以进这个的源码看看,就是一连串的 if
Action 中注入对象只需要实现特定的接口即可:
实现 RequestAware 接口注入 request 中的 map 集合,大多数都是用的这个吧
实现 ServletRequestAware 注入 request 对象
实现 ApplicationAware 注入 Application 对象
实现 ServletContextAware 注入 ServletContext 对象
另外还有 session 的,等等….
那么什么时候用注入对象的方式呢,ActionContext 和 ServletActionContext 都不能在构造器中初始化,因为拦截器是在实例化 Action 之后执行的,如果用的很频繁每个方法都来一遍太 low 了,于是可以使用这种注入的方式保存成全局变量
在后期优化的时候对 BestAction 进行注入也是不错的选择
Token拦截器
在表单防止重复提交中,这个是必用的吧;一般会用到两个域:一个是 page ,一个是 session,对应客户端和服务端吧
相比 Struts1 在 Struts2 中使用更加的简单:
首先在 JSP 页面添加 Token 标签,它会在被访问的时候创建令牌,是在 form 里面哦,另外最后也加个显示错误的标签,因为抛的错误是 Action 错误,所以…
1 | <body> |
其次配置 Action 的时候需要指定下栈排除一些不需要校验的方法,然后添加个校验失败跳转的页面
1 | <action name="TokenAction_*" class="com.bfchengnuo.web.action.TokenAction" method="{1}"> |
嗯….基本就是这样了,另外;如果想要替换错误信息为中文,可以在当前目录建立 Action 相关文件,修改 struts.messages.invalid.token
为你想要的值即可
看网上的帖子说还需要在 struts.xml 中修改 struts.custom.i18n.resources 常量;但是我测试不用修改也完全有效,版本好像是 2.3
execAndWait拦截器
常叫做为执行等待拦截器,从名字也可以看出了,就是在执行耗时操作的时候跳转到一个预设的界面,等耗时操作结束后再跳回;这个过程是个伪异步,因为在等待页面是通过定义 meta 的方式进行定时刷新检测的,下面来简单的模拟下,首先是 Action ,非常简单就是让线程休眠下…
1 | public class WaitAction { |
然后就是配套的配置文件了,execAndWait 是不在默认栈里的,以及需要配一个等待页面 wait:
1 | <action name="WaitAction" class="com.bfchengnuo.web.action.WaitAction"> |
那个等待页面和成功页面就不写了,就是很简单的一句话而已,这样就能看出效果了
自定义拦截器
搞一个简单的拦截器测试看看,自定义的拦截器要实现 Interceptor 接口,注意导包:
1 | public class LoginInterceptor implements Interceptor { |
看以看出,逻辑非常简单,判断是否是 LoginAction 如果是就直接放行了,如果不是就判断 session 是否存有用户信息,没有就跳转到登陆界面上去,但是总感觉这样写不是很优雅呢……嘛~先这样吧
优雅一点的可以通过 ActionInvocation 对象获取 Action 的代理对象(ac.getProxy()
),然后通过代理对象可以拿到当前执行 Action 的方法名等信息,然后进行相应的拦截
拦截器的 init 方法是在服务器启动的时候就会执行的(过滤器也是如此),并且一般只执行一次,而 intercept 方法会执行多次;如果你不需要初始化或清理代码,可以继承自扩展的 AbstractInterceptor 类,它提供了一个对 init() 和 destroy() 方法的默认的无操作实现。
接下来是测试 Action 了
1 | public class LoginAction extends ActionSupport implements SessionAware { |
与之相关的配置文件:
1 | <package name="Login" extends="struts-default" namespace="/login"> |
嗯….这样应该就差不多了吧….
需要注意的是:配置自定义拦截器(interceptors)要放在 package 中的最前面,如果使用 default-interceptor-ref 那么所有的 Action 都会执行其中定义的栈,如果只需要特定的 Action 执行自定义的栈,可以在相应的 Action 中进行配置,这样其他的 Action 就还是执行默认的栈。
OGNL表达式
OGNL 也是一个开源的项目,Struts2 融合了 OGNL 并且将它作为默认的表达式语言,它必须依赖于 Struts 的标签才能使用
OGNL 由表达式语言和类型转换器组成,就是为了方便数据的访问;总体来说和 EL 表达式是很像的,并且兼容 EL 表达式,大多数表达式不需要进行转义,如果用到了使用 %{xxx}
进行转义,也叫做强制表达式解析(在不自动进行 OGNL 解析的地方强制解析),多用于非 String 对象
举个简单使用的栗子,首先是主要的 JSP 页面部分:
1 | <body> |
在测试的时候还掉进了一个坑,就上面所示: 如果直接写 names 默认会存一个值(数组大小也被置为1),但是不要 names 和 names[0] 同时出现,这样会被覆盖,当时就被坑了好久…..竟然犯这样的低级错误
数组、集合都可以和 EL 一样使用,属性链也 OK;当然 map 也是可以的:
map 的 key 如果是字符串可以直接用 .
连接:map.key.name
;否则就使用 map['key'].name
这样的形式
我是把把表单提交后的页面再指向回此 JSP ,这样数据会回显,以此来观察效果
相关的 Action 没啥好说的,就是定义上面所述的属性,List、Map 可以不指定泛型,在 OgnlAction-conversion.properties
Action 相关文件中指定,如果指定了泛型,那么就会自动进行转换的,转换并不只是八种基本类型,还要强大些,关于 conversion 文件,这是指定前台提交的数据如何进行转换的,毕竟前台提交的都是 String:
1 | # 指定 List 内装的是什么类型 |
通过这个文件就可以确定前面表单中的 nameList[0].name
指的就是 List 中第一个 user 对象中的 name 属性;
表单中还有一个 name 叫 user 的,这个是我编写的一个简单的 javabean,正常来说需要这样写:user.name
才能正确的赋值,总不能把一个字符串赋值给一个 user 对象吧,如果硬要这样那就必须定义一个转换器了,就是上面 properties 文件指定的那个类
1 | public class UserConverter extends StrutsTypeConverter { |
定义转换器需要继承 StrutsTypeConverter ,复写两个方法,一个是用于客户端提交数据时转换,一个是服务器返回数据回显时转换;它和值栈挨着,提交数据时经过转换后存到值栈,回显数据时经过转换输出到客户端
对于转换器来说,每个 Action 都搞一个太累,可以在 src 根目录下定义
xwork-conversion.property
全局的类型转换器
OGNL中重要的3个符号
#
、%
和 $
符号在 OGNL 表达式中经常出现,他们的意义嘛
#
它的用途一般有三种,最常用的就是用来访问非根对象属性 ,比如:#session/#request/#application
等;#
相当于ActionContext.getContext()
默认会自动搜索各个域
第二种是用于过滤和投影(projecting)集合
第三种是用来构造集合,Map 和 List :#{key1:val1,key2:val2}/{1,2,3}
%
% 符号的用途是在标志的属性为字符串类型时(或者说值类型的标签中),计算 OGNL 表达式的值,相当于提供了一个 OGNL 的解析环境,就是所谓的强制(暴力)解析了;
如果本来默认解析 OGNL 的标签(对象类型标签)想让其单纯的输出值,就需要在双引号里加一层单引号$
主要用途有两个:在国际化资源文件中,引用 OGNL 表达式;在 Struts2 配置文件中,引用 OGNL 表达式
比如在使用重定向时,传参只能通过 url;通过这个就可以获得 Action 中的数据
关于值栈
OGNL 和值栈有密切的关系,既然用户要获取一些必要的数据(JSP 中)那么怎么才能把 Action 中的数据传给用户呢,就是用的值栈
Action 创建的同时也会创建值栈以及 ActionContext,然后会把 Action 对象封装进值栈中(在根元素),通过 Request 传给用户(key 为 struts.ValueStak);获取值栈可以通过 ActionContext,也可以通过 Request
值栈当中维护了一个 OgnlContext 对象(request、session 等对象的数据就存在这里,Action 对象在根元素),就是 OGNL 表达式所用的,其实是一个 Map
从上面可以看出,Action 是放进了值栈的根节点,那么 Action 中的属性也毫无疑问的放在了根节点,而 OGNL 表达式取根节点的值是不需要加 #
符号的,所以可以使用 # + 属性名
直接取
OGNL标签
如同 JSTL ,OGNL 也自带了一些标签供使用;首先还要明确一点:
struts2 接收到请求后马上创建一个 ActionContext,一个 ValueStak 和一个 Action 对象;Action 对象立即放到值栈上便于 OGNL 的访问
顶层对象会覆盖底层对象,也就说从上往下找,找到了就不会继续往下了
默认是从栈顶的对象里寻找,是栈顶对象!所以,一般是 Action 在栈顶;但是如果 user 对象在栈顶(前面所说的模型驱动),可以直接用其属性
OGNL 不仅可以访问 bean 的属性,还可以访问方法,但是不建议用
Struts2 的标签库都是使用 OGNL 表达式来访问 ActionContext 中的对象数据的;并且将 ActionContext 设置为 OGNL 上下文,默认的根对象为值栈
s:property
首先是有关获取数据的几个标签,也就 ActionContext 中的六个“域”,我事先在 Action 初始化了一波数据,后面会用到
1 | public String execute() { |
然后在 JSP 页面中直接获取就可以了
1 | <body> |
强制表达式解析前面已经提过了,就是用在默认不会进行 OGNL 解析的地方,让其强制进行解析;
还有那个输出常量,加单引号,就是不让其进行 OGNL 解析,但是内容会被 Html 编码,这里我就被坑了…..请记得:在默认进行解析的属性中,如果不使用 OGNL 的时候记得加单引号!
s:set
它是用来存储数据的,并且可以存储到指定的域中;如果没有指定范围则默认保存在 ActionContext 的大 Map 中,可以使用 #name
来获取,也可以不加 # ,那样就会先从值栈中搜寻,找不到了再去大 Map 中去找
1 | <s:set var="s_setName" value="'loli'"/> |
无论是从那个域取数据,如果找不到最终都会到这个大 Map 自身中去找,
当然还可以存取 List:<s:set name="miloList" value="{‘java’,’php’,’C#’}"/>
#
可以理解为是 ActionContext.getContext()
s:push
同样是用来存储数据的,将对象放到值栈的栈顶,标签结束后会自动删除,不过不能指定存储位置,必须是值栈
1 | <s:push value="'Lolicon'"> |
property 不指定 value 的话会自动获取栈顶的对象(值栈),并且会调用其的 toString 方法;另外,不使用 OGNL 的话千万记得加单引号!
s:bean
创建新的 javabean 到栈顶(说的当然是值栈);如果指定了 var 属性,同时还会保存引用到 ActionContext 中,如果不指定,在标签结束后从值栈移除引用后就无法取得数据了
1 | <s:bean name="com.bfchengnuo.domain.User" var="mybean"> |
在 s:bean 标签中的 property 可以直接写 属性名 获取数据….
s:action
作用就是在 JSP 中直接调用某个 Action,executeResult 属性可以指定是否把页面包含进来
1 | <s:action name="HelloWorldAction" namespace="/one" executeResult="true"> |
在调用的时候我还传了几个参数过去…..
在调用 Action 的时候是在一个线程中,也是一个栈中,后来的 Action 在栈顶 (HelloWorldAction)
s:iterator
迭代器….已经很熟了吧,在 JSTL、Struts1 中都见过,就是用来迭代集合的,包括 Map;Struts2 中的迭代器也很好用,非常强大
每次迭代都会把数据放在栈顶
1 | <table border="0" cellspacing="1" bgcolor="#db7093"> |
status 这个对象确实能省不少事呢
s:if/s:elseif/s:else
这个标签就爽了,相比 JSTL 里的简直太爽,可想像在写 java 一样
1 | <s:set var="age" value="14"/> |
以后做逻辑判断就简单多了
需要注意的是,第一行代码的 set 没有用把数字放单引号里,这说明它是一个整型,在使用的时候记得加 #
s:url
在前面的标签库里也见过,就是用来构件 url 的,避免重复写那繁琐的代码,同样;如果加 var 属性就会存到大 Map 中去;最简单的:<s:url />
表示当前的地址
1 | 当前地址(带参数):<s:url includeParams="all"/> <br> |
除了访问 Action 可以直接在 value 属性里写 http 地址
其他
在迭代的时候,可以快速的定义集合,使用 {}
1 | <s:iterator value="{'loli','2','ll','gt'}"> |
对于 Map,也差不多,不过需要加 #
,也就是这样:#{'key':'val','key2':'val'}
使用 property 标签取的时候直接 value=key 、 value=value 这样就行了,因为放在栈顶的就是 Entry 啊,其中就有 getKey、getValue 这样的方法啊
为了证明可以调用方法,可以这样调用看看:
1 | nameList.size: <s:property value="nameList.size"/> <br> |
如果需要调用静态内容,需要指定包名,类似:@java.util.Locale@CHINA
调用静态对象的有参方法:@[email protected](@java.util.locale@CHINA)
注意:静态方法使用 @
连接,如果非静态用 .
就可以了,上面栗子是调用的静态对象 CHINA 中的非静态方法 getDisplayName
由于 Math 比较常用,默认是可以省略的,比如:@@floor(10.09)
除此之外,还有一些 UI 标签,比如上面用到的 <s:form action=''>
,总的来说其实和 html 标签并没有什么区别,并且应该是更方便的,上面的这个 form 标签直接在 action 中填 /actionName
就好了,不需要那一串了
但是在使用 s:a
标签的时候地址写上 /
反而不正确了,而上面的 form 标签就不会;至于到底加不加 /
最好的方法就是试一下了…..emmmm
使用这些标签的时候总是会带一些比较迷的样式(虽然可以设置 theme 属性为 simple),所以一般情况下这类标签是不用的,除非用到了其特别的功能
<s:textfield name='' />
标签会自动回显数据,也就是会自动根据 name 属性来从值栈(只有根元素中才是栈,其他的域都是在 Map 中)获取相应的值,某些时候,只需要在值栈中 push 相应的数据就可以了
有一点还是在这里写下吧,大概:
OGNL 中还可以使用类似 [1].user.name
来获取数据,意思就是栈中第二个对象的 user 对象的 name 属性
OGNL过滤和投影
不想说太多,因为我还没看到….
?
:选择满足条件的所有元素^
:选择满足条件的第一个元素$
:选择满足条件的最后一个元素
语法为:collection.{?expression}
或 collection.{^expression}
或 collection.{$expression}
;栗子大概是这样的:
1 | <s:property value="array.{?#this > 5}"/> |
如果把集合中的数据想象成是数据库表中的数据,投影简单说就是从这表中选取某一列所构成的一个新的集合,感觉和 VO 差不多;投影的语法:collection.{expression}
;比如: <s:property value="personList.{name}"/>
多数情况下,过滤和投影是联合起来用的,使用起来还算简单:
1 | <s:property value="personList.{?#this.sex.equals('female')}.{name}"/> |
使用注解
如果厌倦了在配置文件中写一大堆的配置,可以选择使用注解,当然有利有弊,在前面的文章已经说过,下面来看看常用的注解:
另:使用注解需要 struts2-convention-plugin
的 jar 包
1 | "struts-default") ( |
大体就是这样使用了….. @Action(url) 中的url,如果以 /
开头那是绝对路径,访问的时候不需要加命名空间,否则就要加命名空间,应该是吧,未测试
常用的注解:
Namespace:指定命名空间。
ParentPackage:指定父包。
Result:提供了Action结果的映射。(一个结果的映射)
Results:“Result”注解列表
ResultPath:指定结果页面的基路径。
Action:指定Action的访问URL。
Actions:“Action”注解列表。
ExceptionMapping:指定异常映射。(映射一个声明异常)
ExceptionMappings:一级声明异常的数组。
InterceptorRef:拦截器引用。
InterceptorRefs:拦截器引用组。
其他的一些用到再补充吧,比如那些拦截注解 Before 、After 之类的,类型转换注解啊。。。总之;注解能做很多事
其他补充
当我们需要把返回的内容转成 JSON 时,可以使用 Struts 提供的插件完成,导包就不说了(struts2-json-plugin)
配置文件中的 package 继承 json-default,返回值的 type 填 json ;
在 Action 中对应的方法中,把相应的数据保存到集合里,通常使用 List,这个集合要是全局的才行,因为要提供 getter/setter 方法,这样 Struts 就会自动进行处理了
参考
http://wiki.jikexueyuan.com/project/struts-2/annotations-types.html
http://www.blogjava.net/fancydeepin/archive/2014/03/17/struts-ognl.html
评论框加载失败,无法访问 Disqus
你可能需要魔法上网~~