Mybatis拦截器并不是每个对象里面的方法都可以被拦截的。Mybatis拦截器只能拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler四个类里面的方法,这四个对象在创建的时候才会创建代理。
用途:实际工作中,可以使用Mybatis拦截器来做一些SQL权限校验、数据过滤、数据加密脱敏、SQL执行时间性能监控和告警等。
1.使用方法
以在Spring中创建 StatementHandler.update()方法的拦截器为例:
@Component
@Order(1)
@Intercepts({@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),})
public class SqlValidateMybatisInterceptor extends PRSMybatisInterceptor {@Overrideprotected Object before(Invocation invocation) throws Throwable {String sql="";Statement statement=(Statement) invocation.getArgs()[0];if(Proxy.isProxyClass(statement.getClass())){MetaObject metaObject= SystemMetaObject.forObject(statement);Object h=metaObject.getValue("h");if(h instanceof StatementLogger){RoutingStatementHandler rsh=(RoutingStatementHandler) invocation.getTarget();sql=rsh.getBoundSql().getSql();}else {PreparedStatementLogger psl=(PreparedStatementLogger) h;sql=psl.getPreparedStatement().toString();}}else{sql=statement.toString();}if(containsDelete(sql)&&!containsWhere(sql)){throw new SQLException("不能删除整张表,sql:"+sql);}return null;}private boolean containsDelete(String sql){return sql.contains("delete")||sql.contains("DELETE");}private boolean containsWhere(String sql){return sql.contains("where")||sql.contains("WHERE");}
}
public class PRSMybatisInterceptor implements Interceptor {Boolean needBreak=false;@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object result= before(invocation);if(needBreak){return result;}result= invocation.proceed();result=after(result,invocation);return result;}protected Object before(Invocation invocation) throws Throwable{return null;}protected Object after(Object result,Invocation invocation) throws Throwable{return result;}@Overridepublic Object plugin(Object o) {return Plugin.wrap(o, this);}@Overridepublic void setProperties(Properties properties) {}
}
1. 自定义拦截器 实现 org.apache.ibatis.plugin.Interceptor 接口与其中的方法。在plugin方法中需要返回 return Plugin.wrap(o, this)。在intercept方法中可以实现拦截的业务逻辑,改方法的 参数 Invocation中有原始调用的 对象,方法和参数,可以对其任意处理。
2. 在自定义的拦截器上添加需要拦截的对象和方法,通过注解 org.apache.ibatis.plugin.Intercepts 添加。如示例代码所示:
Intercepts的值是一个签名数组,签名中包含要拦截的 类,方法和参数。
2.MyBatis对象的创建
代理对象指的是:可以被拦截的4个类的实例。
代理对象创建时需要解析拦截器,从而利用JDK动态代理将拦截器的逻辑织入原始对象。
DefaultSqlSession中依赖Executor,如果新建的时候会创建executor
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {...final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (cacheEnabled) {executor = new CachingExecutor(executor);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;
}
Executor中要用StatementHandler执行sql语句,StatementHandler是调用configuration.newStatementHandler()方法创建的。
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;
}
StatementHandler依赖 parameterHandler 和 resultSetHandler,在构造 StatementHandler 时会调用一下方法创建这两个 handler。
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;
}
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;
}
3.代理对象的创建
3.1 拦截器的获取
从对象的创建过程中可以看出 代理 对象的创建时通过 InterceptorChain.pluginAll() 方法创建的。
查看 拦截器链 InterceptorChain 发现,其中的拦截器的添加是在 Configuration 中。因为拦截器被声明为Bean了,所以在MyBatis初始化的时候,会扫描所有拦截器,添加到 InterceptorChain 中。
3.2 代理对象的创建
从上一步得知代理对象的创建是调用 Interceptor.pugin() 方法,然后调用 Plugin.wrap() 方法
Interceptor
@Override
public Object plugin(Object o) {return Plugin.wrap(o, this);
}
Plugin实现了 InvocationHandler 接口
在 Plugin.wrap() 方法中会获取当前拦截器的接口,生成动态代理。
4. 拦截器的执行过程
在动态代理中当代理对象调用方法时,会将方法的调用委托给 InvocationHandler,也就是 Plugin,如下图所示
在该方法中 获取拦截器签名中的方法,如果包含当前方法,则调用拦截方法,否则执行原方法的调用。
5. 拦截器的执行顺序
拦截器的顺序配置使用 Spring 中的 org.springframework.core.annotation.Order 注解配置。
order值大的拦截器先执行,order值大的在interceptors中越靠后,最后生成代理,所以先执行。
6. 拦截器示例
此拦截器为一个通用的拦截器,封装了具体的拦截器实现,开放出来before和after方法供业务调用。
public class PRSMybatisInterceptor implements Interceptor {Boolean needBreak = false;@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object result = before(invocation);if (needBreak) {return result;}result = invocation.proceed();result = after(result, invocation);return result;}protected Object before(Invocation invocation) throws Throwable {return null;}protected Object after(Object result, Invocation invocation) throws Throwable {return result;}@Overridepublic Object plugin(Object o) {return Plugin.wrap(o, this);}@Overridepublic void setProperties(Properties properties) {}
}
/*** @author CaptHua*/
@Component
@Order(1)
@Intercepts({@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),})
public class SqlValidateMybatisInterceptor extends PRSMybatisInterceptor {@Overrideprotected Object before(Invocation invocation) throws Throwable {String sql = "";Statement statement = (Statement) invocation.getArgs()[0];if (Proxy.isProxyClass(statement.getClass())) {MetaObject metaObject = SystemMetaObject.forObject(statement);Object h = metaObject.getValue("h");if (h instanceof StatementLogger) {RoutingStatementHandler rsh = (RoutingStatementHandler) invocation.getTarget();sql = rsh.getBoundSql().getSql();} else {PreparedStatementLogger psl = (PreparedStatementLogger) h;sql = psl.getPreparedStatement().toString();}} else {sql = statement.toString();}if (containsDelete(sql) && !containsWhere(sql)) {throw new SQLException("不能删除整张表,sql:" + sql);}return null;}private boolean containsDelete(String sql) {return sql.contains("delete") || sql.contains("DELETE");}private boolean containsWhere(String sql) {return sql.contains("where") || sql.contains("WHERE");}
}