struts2 spring3.2.4 mybatis-3.2.3 通用分页(不同数据库)拦截器
mybatis没有根据数据库方言进行分页封装,但是提供了拦截器,我们可以在拦截其中获取数据库方言(数据库方言通过配置文件获取)和查询sql,根据数据库方言进行翻页分装,在拦截其中有两种实现方式:
1. 拦截器中计算总数,通过jdbc的方式,再封装翻页sql
2.计算总数在应用层计算,拦截器中只通过数据库方言进行查询sql的封装本人认为第一种方式可能会影响到性能,下面是通过第二种方法实现分页
1、拦截器类
[java]view plaincopyprint?
1.package com.zhou.bean;
2.
3.import https://www.sodocs.net/doc/ae11567884.html,ng.reflect.Field;
4.import java.sql.Connection;
5.import java.sql.PreparedStatement;
6.import java.sql.ResultSet;
7.import java.sql.SQLException;
8.import java.util.List;
9.import java.util.Properties;
10.
11.import org.apache.ibatis.executor.parameter.ParameterHandler;
12.import org.apache.ibatis.executor.statement.RoutingStatementHandler;
13.import org.apache.ibatis.executor.statement.StatementHandler;
14.import org.apache.ibatis.mapping.BoundSql;
15.import org.apache.ibatis.mapping.MappedStatement;
16.import org.apache.ibatis.mapping.ParameterMapping;
17.import org.apache.ibatis.plugin.Interceptor;
18.import org.apache.ibatis.plugin.Intercepts;
19.import org.apache.ibatis.plugin.Invocation;
20.import org.apache.ibatis.plugin.Plugin;
21.import org.apache.ibatis.plugin.Signature;
22.import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
23.
24./**
25.* @author zhouzhenlong
26.* @date 2012-11-20 下午04:33:35
27.* @description 不在拦截器中计算总数,影响效率
28.* @version V1.0
29.*
30.* 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。利用拦截器实
现Mybatis分页的原理:
31.* 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象
32.* ,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象,而且对应的
Sql语句
33.* 是在Statement之前产生的,所以我们就可以在它生成Statement之前对用来生成
Statement的Sql语句下手
34.* 。在Mybatis中Statement语句是通过RoutingStatementHandler对象的
35.* prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截
StatementHandler接口的prepare方法
36.* ,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用
37.* StatementHandler对象的prepare方法,即调用invocation.proceed()。
38.* 对于分页而言,在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共
有多少
39.* ,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好
的参数和设
40.* 置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记
录数的统计。
41.*
42.*/
43.@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class,
args = { Connection.class }) })
44.public class PageInterceptor implements Interceptor {
45.
46.private String databaseType;// 数据库类型,不同的数据库有不同的分页方法
47.
48./**
49.* 拦截后要执行的方法
50.*/
51.public Object intercept(Invocation invocation) throws Throwable {
52.// 对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另
一个是抽象类BaseStatementHandler,
53.// BaseStatementHandler有三个子类,分别是SimpleStatementHandler,
PreparedStatementHandler和CallableStatementHandler,
54.// SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler
是处理PreparedStatement的,而CallableStatementHandler是
55.// 处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的
RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个
56.// StatementHandler类型的delegate属性,RoutingStatementHandler会依据
Statement的不同建立对应的BaseStatementHandler,即SimpleStatementHandler、57.// PreparedStatementHandler或CallableStatementHandler,在
RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的
delegate对应的方法。
58.// 我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截
StatementHandler接口的prepare方法,又因为Mybatis只有在建立
RoutingStatementHandler的时候
59.// 是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是
RoutingStatementHandler对象。
60.final RoutingStatementHandler handler = (RoutingStatementHandler)
invocation.getTarget();
61.// 通过反射获取到当前RoutingStatementHandler对象的delegate属性
62.final StatementHandler delegate = (StatementHandler)
ReflectUtil.getFieldValue(handler, "delegate");
63.// 获取到当前StatementHandler的
64.// boundSql,这里不管是调用handler.getBoundSql()还是直接调用
delegate.getBoundSql()结果是一样的,因为之前已经说过了
65.// RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的
delegate对应的方法。
66.final BoundSql boundSql = delegate.getBoundSql();
67.// 拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数
对象
68.final Object obj = boundSql.getParameterObject();
69.// 这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
70.if (obj instanceof SearchPageUtil) {
71.final SearchPageUtil page = (SearchPageUtil) obj;
72.// 通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
73.// MappedStatement mappedStatement = (MappedStatement)
74.// ReflectUtil.getFieldValue(delegate, "mappedStatement");
75.// 拦截到的prepare方法参数是一个Connection对象
76.// Connection connection = (Connection) invocation.getArgs()[0];
77.// 获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
78.final String sql = boundSql.getSql();
79.// 给当前的page参数对象设置总记录数影响性能
80.// this.setTotalRecord(page, mappedStatement, connection);
81.// 获取分页Sql语句
82.final String pageSql = this.getPageSql(page, sql);
83.// 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
84.ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
85.}
86.return invocation.proceed();
87.}
88.
89./**
90.* 拦截器对应的封装原始对象的方法
91.*/
92.public Object plugin(Object target) {
93.return Plugin.wrap(target, this);
94.}
95.
96./**
97.* 设置注册拦截器时设定的属性
98.*/
99.public void setProperties(Properties properties) {
100.this.databaseType = properties.getProperty("databaseType"); 101.}
102.
103./**
104.* 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle 其它的数据库都没有进行分页
105.*
106.* @param page
107.* 分页对象
108.* @param sql
109.* 原sql语句
110.* @return
111.*/
112.private String getPageSql(SearchPageUtil page, String sql) {
113.final StringBuffer sqlBuffer = new StringBuffer(sql);
114.if ("mysql".equalsIgnoreCase(databaseType)) {
115.return getMysqlPageSql(page, sqlBuffer);
116.} else if ("oracle".equalsIgnoreCase(databaseType)) {
117.return getOraclePageSql(page, sqlBuffer);
118.}
119.return sqlBuffer.toString();
120.}
121.
122./**
123.* 获取Mysql数据库的分页查询语句
124.*
125.* @param page
126.* 分页对象
127.* @param sqlBuffer
128.* 包含原sql语句的StringBuffer对象
129.* @return Mysql数据库分页语句
130.*/
131.private String getMysqlPageSql(SearchPageUtil page, StringBuffer sqlBuffer) {
132.// 计算第一条记录的位置,Mysql中记录的位置是从0开始的。
133.// int offset = (page.getPage().getPageIndex() - 1) *
134.// page.getPageSize();
135.sqlBuffer.append(" limit
").append(page.getStartRow()).append(",").append(page.getPageSize());
136.return sqlBuffer.toString();
137.}
138.
139./**
140.* 获取Oracle数据库的分页查询语句
141.*
142.* @param page
143.* 分页对象
144.* @param sqlBuffer
145.* 包含原sql语句的StringBuffer对象
146.* @return Oracle数据库的分页查询语句
147.*/
148.private String getOraclePageSql(SearchPageUtil page, StringBuffer sqlBuffer) {
149.// 计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的
150.final int offset = (page.getPage().getPageIndex() - 1) * page.getPageSize() + 1;
151.sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ")
152..append(offset + page.getPageSize());
153.sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
154.// 上面的Sql语句拼接之后大概是这个样子:
155.// select * from (select u.*, rownum r from (select * from t_user) u 156.// where rownum < 31) where r >= 16
157.return sqlBuffer.toString();
158.}
159.
160./**
161.* 给当前的参数对象page设置总记录数
162.*
163.* @param page
164.* Mapper映射语句对应的参数对象
165.* @param mappedStatement
166.* Mapper映射语句
167.* @param connection
168.* 当前的数据库连接
169.*/
170.private void setTotalRecord(SearchPageUtil page, MappedStatement mappedStatement, Connection connection) {
171.// 获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。
172.// delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。
173.final BoundSql boundSql = mappedStatement.getBoundSql(page);
174.// 获取到我们自己写在Mapper映射语句中对应的Sql语句
175.final String sql = boundSql.getSql();
176.// 通过查询Sql语句获取到对应的计算总记录数的sql语句
177.final String countSql = this.getCountSql(sql);
178.// 通过BoundSql获取对应的参数映射
179.final List
boundSql.getParameterMappings();
180.// 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
181.final BoundSql countBoundSql = new
BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, 182.page);
183.// 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
184.final ParameterHandler parameterHandler = new
DefaultParameterHandler(mappedStatement, page, countBoundSql);
185.// 通过connection建立一个countSql对应的PreparedStatement对象。
186.PreparedStatement pstmt = null;
187.ResultSet rs = null;
188.try {
189.pstmt = connection.prepareStatement(countSql);
190.// 通过parameterHandler给PreparedStatement对象设置参数
191.parameterHandler.setParameters(pstmt);
192.// 之后就是执行获取总记录数的Sql语句和获取结果了。
193.rs = pstmt.executeQuery();
194.if (rs.next()) {
195.final int totalRecord = rs.getInt(1);
196.// 给当前的参数page对象设置总记录数
197.page.getPage().setRowTotal(totalRecord);
198.}
199.} catch (SQLException e) {
200. e.printStackTrace();
201.} finally {
202.try {
203.if (rs != null)
204.rs.close();
205.if (pstmt != null)
206.pstmt.close();
207.} catch (SQLException e) {
208. e.printStackTrace();
209.}
210.}
211.}
212.
213./**
214.* 根据原Sql语句获取对应的查询总记录数的Sql语句
215.*
216.* @param sql
217.* @return
218.*/
219.private String getCountSql(String sql) {
220.final int index = sql.indexOf("from");
221.return "select count(*) " + sql.substring(index); 222.}
223.
224./**
225.* 利用反射进行操作的一个工具类
226.*
227.*/
228.private static class ReflectUtil {
229./**
230.* 利用反射获取指定对象的指定属性
231.*
232.* @param obj
233.* 目标对象
234.* @param fieldName
235.* 目标属性
236.* @return 目标属性的值
237.*/
238.public static Object getFieldValue(Object obj, String fieldName) { 239.Object result = null;
240.final Field field = ReflectUtil.getField(obj, fieldName);
241.if (field != null) {
242.field.setAccessible(true);
243.try {
244.result = field.get(obj);
245.} catch (IllegalArgumentException e) {
246.// TODO Auto-generated catch block
247. e.printStackTrace();
248.} catch (IllegalAccessException e) {
249.// TODO Auto-generated catch block
250. e.printStackTrace();
251.}
252.}
253.return result;
254.}
255.
256./**
257.* 利用反射获取指定对象里面的指定属性
258.*
259.* @param obj
260.* 目标对象
261.* @param fieldName
262.* 目标属性
263.* @return 目标字段
264.*/
265.private static Field getField(Object obj, String fieldName) {
266.Field field = null;
267.for (Class> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
268.try {
269.field = clazz.getDeclaredField(fieldName);
270.break;
271.} catch (NoSuchFieldException e) {
272.// 这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。273.}
274.}
275.return field;
276.}
277.
278./**
279.* 利用反射设置指定对象的指定属性为指定的值
280.*
281.* @param obj
282.* 目标对象
283.* @param fieldName
284.* 目标属性
285.* @param fieldValue
286.* 目标值
287.*/
288.public static void setFieldValue(Object obj, String fieldName, String fieldValue) {
289.final Field field = ReflectUtil.getField(obj, fieldName);
290.if (field != null) {
291.try {
292.field.setAccessible(true);
293.field.set(obj, fieldValue);
294.} catch (IllegalArgumentException e) {
295.// TODO Auto-generated catch block
296. e.printStackTrace();
297.} catch (IllegalAccessException e) {
298.// TODO Auto-generated catch block
299. e.printStackTrace();
300.}
301.}
302.}
303.}
304.
305.}
2、拦截器配置
在配置文件mybatis-config.xml中增加
[html]view plaincopyprint?
1.
2.
3.
4.
5.
6.
3、应用
在配置文件test-mapper.xml去掉查询方法getList的翻页语句limit #{startRow},#{pageSize},部署到tomcat下访问http://localhost:8080/Test/test.jsp点击列表页面按钮,进入列表页面,翻页功能正常使用。
--------------------------------------装--------------------------------------订------------------------------线---------------------------------------- **学院课程考试试卷 课程名称:《使用Struts2开发基于MVC设计模式的企业级应用》(A)卷 年级:班级: 姓名:_______________ 学号:_________________ 考试(考查) 闭卷 选择题(每题2分,共计100分) 1.在控制器类中一般需要添加相应属性的( A )和(C )。(选两项) A.setter方法 B.as方法 C.getter方法 D.is方法 2.业务控制器需要在( B )配置文件中进行配置 A.web.xml B.struts.xml C.struts2.xml D.webwork.xml 3.不属于Struts 2表单标签库的是( D )。 A.
1、自定义拦截器,实现对注册页面上的文字信息进行过滤拦截,不允许出现字符集合中{“佛法”,”集会”,”党派”}的文字信息。如出现,则返回到注册页面,重新填写。 2、定义拦截器,实现登录检查。由于在项目开发时,需要对大多数的页面进行登录检查。当没有登录就无法进行操作,并返回到登录页面。为减少代码量利用Struts2中自定义拦截器的功能,实现登录检查。(避免不登录直接访问某个Action) Web.xml
Struts2拦截器详细配置过程 1:所有拦截器的超级接口Interceptor,拦截器去实现这个接口; Interceptor它其中有三个方法 (init(),destroy(),interceptor()):Init()方法:在服务器起动的时候加载一次,并且只加载一次; Destroy()方法:当拦截器销毁时执行的方法; Interceptor()方法:其中里边有一个参数invocation public String intercept(ActionInvocation invocation)throws xception { System.out.println("interceptor!!"); String result=invocation.invoke(); return result; }Invocation.invoke()是如果只有一个拦截器执行完这个方法后,会返回给视图,如果有多 个拦截器,它顺序的执行完所有的拦截器,才返回给视图. 2:可以在系统初始化中给拦截器指定默认的参数(也包括了定义拦截器方式)如下:在拦截器类中把hello当做属性set/get方式注入到拦截器类中;
由于struts2标签的性能不好,项目组决定不使用,但是如果用struts2自带的拦截器防止重复提交又必须struts标签,所以只好自定拦器实现,具体步骤如下: 新建拦截器类: public class TokenAtionInterceptor extends AbstractInterceptor { public String intercept(ActionInvocation invocation) throws Exception { Map
题目1 以下不届丁 Struts2中result 的type 届性() ? A. action B. redirect 题目2 下歹0有关拦截器说法错误的是? 「A.struts 通过拦截器完成执行action 请求处理方法前一系歹U 操作。例如: 数据封装、文件上传、数据校验等 'B.在struts 中,直接访问jsp 页面,struts 将使用默认拦截器栈处理当前 请求。 厂C.在执行action 时,struts 将执行若干拦截器1、2、3,执行action 完成 后,将继续执行拦截器3、2、1 'D.默认情况,在一个action 没有配置拦截器的引用,说明当前action 将不 使用拦截器 题目3 以下哪些是Action 接口提供的返回值? W A A. success ,D B. none C. error 财 D.input 题目4 如果要实现struts2的数据检验功能 广A 普通的Action 类可以实现 C. redirectAction D. dispatcher
「B继承自Action接口的可以实现 面C继承自ActionSupport类可以实现 厂D继承自ActionValidate 类可以实现 题目5 struts2默认的处理结果类型是: ? A.dispatcher ' B.redirect 「C.chain D. forward 题目6 在值栈的上下文Context中,存在一些固定的key表示不同的对象,以下描述正确的是? A. request,表示request作用域的数据 'B.session,表示session 作用域的数据 阿 C.application ,表示application 作用域的数据 * D.parameters ,表示请求参数的所有数据 题目7 以下届丁struts2配置文件中的配置元素是:()多选) A.
Yaio 4 一、准备工作及实例 4 1.解压struts- 2.1.6-all.zip 4 2.六个基本包 4 3.初识struts2配置文件 4 (1).web.xml文件 4 (2).struts.xml文件 4 (3).struts.properties(参default.properties) 4 (4)struts-default.xml 5 (5)其它配置文件 5 4.让MyEclipse提示xml信息 5 5.如何使用alt+/提示 5 6.实例 5 7.开启struts2自带的开发模式常量 7 8.vo传参模式 8 9.ModerDriven传参模式(不建议采用) 8 10.为什么要使用struts2代替struts1.x 9
二、struts.xml配置及例程 9 1.配置文件的优先级 9 2.配置形式 9 3.package配置相关 10 4.分工合作include:指定多个配置文件 12 5.tomcat认证访问 12 6.初识拦截器 13 7.Action中的method属性 15 8.使用ForwardAction实现页面屏蔽。 17 8.使用default-Action配置统一访问 17 小结Action 18 9.使用通配符 18 10.使用0配置:ZERO Annotation 19 11.Result配置详解 19 12.异常处理 24 三、在Action获取Scope对象 25 方式一、与Servlet解耦合的非IOC方式 25
方式二、与Servlet解耦合的IOC方式 27 方式三、与Servlet耦合的非IOC方式 27 方式四、与Servlet耦合的IOC方式 28 四、OGNL与ValueStack(VS) 29 1.值栈入门 29 2.OGNL入门 31 3.普通方法访问 31 4.静态方法访问 31 5.默认类Math的访问 32 6.调用普通类的构造方法 32 7.集合对象初步 32 8.集合对象进阶 33 9.N语法top语法 34 10.获取Stack Context中的信息 35 11.总结$ # %的区别 35 12.总结OGNL[重点] 35 五、拦截器 36
Struts2中struts.xml的Action配置详解 使用package可以将逻辑上相关的一组Action,Result,Interceptor等组件分为一组,Package 有些像对象,可以继承其他的Package,也可以被其他package继承,甚至可以定义抽象的Package。 由于struts.xml文件是自上而下解析的,所以被继承的package要放在继承package的前边。Namespace将action分成逻辑上的不同模块,每一个模块有自己独立的前缀。使用name space可以有效的避免action重名的冲突,例如每一个package都可以有自己独立的Men u和Help action,但是事项方式各有不同。Struts2标签带有namespace选项,可以根据namespace的不同向服务器提交不同的package的action的请求。 “/”表示根namespace,所有直接在应用程序上下文环境下的请求(Context)都在这个pa ckage中查找。 “”表示默认namespace,当所有的namespace中都找不到的时候就在这个namespace中寻找。 例如,有如下配置: CODE:
拦截器是在面向切面编程的就是在你的service或者一个方法,前调用一个方法,或者在方法后调用一个方法,比如动态代理就是拦截器的简单实现,过滤器是在java web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的 action进行业务逻辑,比如 过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者 struts的action前统一设置字符集,或者去除掉一些 非法字符。 拦截器与过滤器的区别: 1、拦截器是基于java的反射机制的,而过滤器是基于函数回调 2、过滤器依赖与servlet容器,而拦截器不依赖与servlet容器 3、拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用 4、拦截器可以访问action上下文、值栈里的对象,而过滤器不能 5、在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次 过滤器 也就是写一个自己的类,让这个类实现于Filter这个接口,这个接口里有三个方法。 init(),doFilter(),destroy();主要是对doFilter()进行操作,你可以 在这个方法里写你想进行的操作. 写完这些之后,就是在web.xml里的配置了
结果类型 Action处理完用户请求后,将返回一个普通的字符串,整个普通字符串就是一个逻辑视图。Struts2通过配置一个逻辑视图和物理视图的映射关系,一旦系统返回某个逻辑视图系统就会把对应的物理视图呈现给用户。 Struts2 在struts.xml中使用
com.opensymphony.xwork2.interceptor.Interceptor接口: public class PermissionInterceptor implements Interceptor { private static final long serialVersionUID = -5178310397732210602L; public void destroy() { } public void init() { } public String intercept(ActionInvocation invocation) throws Exception { System.out.println("进入拦截器"); if(session里存在用户){ String result = invocation.invoke(); }else{ return “logon”; } //System.out.println("返回值:"+ result); //return result; } }
Struts2 中的拦截器和servelt 中的过滤器是非常的相似的。如果学过过滤器的话,肯定能够感觉的到,尽管有些微的不同。可是struts2的拦截器到底如何使用呢,为什么会有这些配置呢?接下来一一来看。 过滤器和拦截器是非常相似的,过滤器public interface Filter 接口里面有三个方法: ?init(FilterConfig filterConfig), ?destroy(), ?doFilter(ServletRequest request, ServletResponse response, FilterChain chain), 这里面的doFilter() 方法是最重要的,在struts2 中String intercept(ActionInvocation invocation)就相当于此方法。 如何完成一个拦截器呢?在struts2 中要实现一个接口这个接口是什么呢?在哪呢?,是否在哪听说过?是webwork 是我们以前听的最多的关于拦截器的框架,struts2 用了其中一个核心的东西,这个东西在是什么呢?是xwork 。恩,有了它才可以拦截,好了我们在哪找呢?在com.opensymphony.xwork2.interceptor 中找,里面有个Interceptor 这是个接口,里面也有三个方法,有init,destroy 和intercept 三个方法,而在struts2 里面的所有的拦截器都继承这个接口!
为了看这些是怎么实现的,加入了一些打印! 将上面的配置整合起来就是:
这样就可以让Aciton 被拦截了,到此,好了,可以运行程序了: 输出结果是:启动服务器init 被打出 运行后提交action 输出intercept 这个就是初步的一个拦截器。 ======= 在此可能出现一个问题,是什么呢?如果就我们做的注册程序而言,可以想一下,有数据转换,有数据校验,以前当转换和校验不符合的时候,点击提交,会提示相关错误信息,然而,此时当转换和校验依然不符合要求时,点击提交,却不会提示错误信息,为什么呢? ==== 当然你答对了,这些功能都包含在struts2的默认拦截器中,这里没执行是被添加的拦截器myinterceptor取代了。 为了查明原因可以查看一下struts2-core-2.xx.jar中的struts-default.xml 这个文件 这里定义的很多的东西,和我们的程序相关的非常紧密 首先这里有个
Struts S2-045漏洞调试及分析 Auth:Cryin’ Date:2016.3.9 漏洞公告 首先看官方给出的漏洞公告信息: “Possible Remote Code Execution when performing file upload based on Jakarta Multipart parser.” 问题原因: “It is possible to perform a RCE attack with a malicious Content-Type value. If the Content-Type value isn't valid an exception is thrown which is then used to display an error message to a user.” 从公告信息可以得到几个漏洞的重点信息: ●存在漏洞的模块是Jakarta ●漏洞产生的点是恶意的Content-Type头 ●恶意的Content-Type头会造成程序抛出异常,在显示错误消息给用户时造成RCE 补丁对比 查看Struts2版本2.3.32在github上的commit(Uses default error key if specified key doesn't exist)并对比修改内容: https://https://www.sodocs.net/doc/ae11567884.html,/apache/struts/commit/352306493971e7d5a756d61780d57a76eb1 f519a 可以看到对LocalizedTextUtil.findText方法进行了重写,并添加了判断。Struts2RCE漏洞的根本原因是程序将用户可控数据带入OGNL表达式解析并执行,而OGNL(Object Graph Navigation Language)对象图形导航语言是一个功能强大的表达式语言,可以用来获取和设置Java对象的属性,但同时也可以对服务端对象进行修改,绕过沙盒甚至可以执行系统命令。 所以,从补丁分析来看LocalizedTextUtil.findText函数很可能是OGNL表达式的传入点,在调试分析时可在此处下断点跟踪分析。 关于jakarta Jakarta是struts2默认处理multipart报文的解析器,该组件定义在struts-default.xml中: 默认使用org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest类对上传数据进行解析并调用第三方组件commons-fileupload.jar完成上传操作。
理解拦截器 1.1.什么是拦截器: 拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。 在Webwork的中文文档的解释为——拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。 谈到拦截器,还有一个词大家应该知道——拦截器链(Interceptor Chain,在Struts 2中称为拦截器栈Interceptor Stack)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。 1.2.拦截器的实现原理: 大部分时候,拦截器方法都是通过代理的方式来调用的。Struts 2的拦截器实现相对简单。当请求到达Struts 2的ServletDispatcher时,Struts 2会查找配置文件,并根据其配置实例化相对的拦截器对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器。如下图: 2.拦截器的配置 Struts 2已经为您提供丰富多样的,功能齐全的拦截器实现。大家可以至struts2的jar包内的struts-default.xml查看关于默认的拦截器与拦截器链的配置。 Struts2(XWork)提供的拦截器的功能说明:
3.使用拦截器 一旦定义了拦截器和拦截器栈后,就可以使用这个拦截器或拦截器栈来拦截 栈将会失去作用。为了继续使用默认拦截器,所以上面配置文件中手动引入了默认拦截器。 4.自定义拦截器 作为“框架(framework)”,可扩展性是不可或缺的。虽然,Struts 2为我们提供如此丰富的拦截器实现,但是这并不意味我们失去创建自定义拦截器的能力,恰恰相反,在Struts 2自定义拦截器是相当容易的一件事。 4.1.实现拦截器类: 所有的Struts 2的拦截器都直接或间接实现接口 com.opensymphony.xwork2.interceptor.Interceptor。该接口提供了三个方法: 1)void init(); 在该拦截器被初始化之后,在该拦截器执行拦截之前, 系统回调该方法。对于每个拦截器而言,此方法只执行一次。 2)void destroy();该方法跟init()方法对应。在拦截器实例被销毁之前, 系统将回调该方法。