mybatis拦截器 - 969251639/study GitHub Wiki
mybatis的拦截器可以用来实现很多功能,比如最常见的分页拦截等,很方便的去扩展mybatis
mybatis执行sql时最终会获取一个statement的handler
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
...
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, 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;
}
首先看下InterceptorChain这个类
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {//挨个执行拦截器
target = interceptor.plugin(target);//将执行完结果传递给下一个,所以需要将执行结果赋给target
}
return target;
}
public void addInterceptor(Interceptor interceptor) {//添加连接器到链中
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {//获取所有拦截器
return Collections.unmodifiableList(interceptors);
}
}
很明显的可以看到用了一个List来保存所有的Interceptor,然后调用pluginAll可以让整个拦截器链逐个执行,返回最后一个拦截器的执行结果
接下来看拦截器的使用
- 在mybatis的配置中配置plugin节点
<configuration>
...
<plugins>
<plugin interceptor="xxx">
<property name="key" value="value" />
...
</plugin>
...
</plugins>
</configuration>
这样mybatis解析时便可以解析到该节点,并将其加入到拦截器链中
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {//获取所有plugins下的节点
String interceptor = child.getStringAttribute("interceptor");//获取plugin节点的interceptor属性(该属性为插件实现类的全路径)
Properties properties = child.getChildrenAsProperties();//获取配置属性
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();//反射创建插件实例对象
interceptorInstance.setProperties(properties);//为创建好的对象设置属性
configuration.addInterceptor(interceptorInstance);//加入到拦截器链中
}
}
}
-
然后需要在实现类中加入Intercepts注解,并实现Interceptor接口,重写intercept, plugin, setProperties三个方法
@Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}) }) public class LogIntercceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { ... } @Override public Object plugin(Object target) { if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } }@Override public void setProperties(Properties properties) { } }
其中plugin是整个链路的核心,因为它是串起整个拦截器链的实现
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {//挨个执行拦截器
target = interceptor.plugin(target);//将执行完结果传递给下一个,所以需要将执行结果赋给target
}
return target;
}
而且实现一般都是调用下面的代码
Plugin.wrap(target, this);
看下wrap做了什么,然后再回过头来看下为什么要这么写
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
在上面的例子中第一个参数是target是StatementHandler(mybatis可以拦截三种handler,后面讲),第二个参数是拦截器类本身
- 获取拦截器类上面的注解,也就是Signature标签,上面登记着要拦截的方法,参数,handler类型等信息
- 获取目标对象(StatementHandler)的Class
- 动态代理出目标对象的代理类
public class Plugin implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());//根据签名获取是否需要拦截的方法
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));//如果是需要拦截的方法则调用拦截器类的intercept方法
}
return method.invoke(target, args);//如果不需要拦截的方法,直接调用目标类的方法
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
}
所以,mybatis拦截器所能拦截的方法就是在拦截器类的注解上声明
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
Signature[] value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
Class<?> type();//handler类型
String method();//拦截方法
Class<?>[] args();//参数
}
再回过头看这段代码
@Override
public Object plugin(Object target) {//其实写return Plugin.wrap(target, this);也一样
if (target instanceof StatementHandler) {//因为签名中代理的handler是StatementHandler
return Plugin.wrap(target, this);
} else {
return target;
}
}
会发现,如果目标类是StatementHandler,那么就返回一个动态代理类,代理StatementHandler对象,然后循环到下一个拦截器类,还是执行上面那段代码,也会代理之前返回的那个代理类,比如有三个拦截器,那么会嵌套三层代理
然后当执行query方法时,会从最外面的那一层开始调用invoke方法,判断是否需要拦截该方法,需要则进入拦截器的intercept方法,否则进入下一层拦截器,当进入intercept方法后最后也需要调用invocation.proceed();方法继续进入下一层拦截器,最后到最终的目标类,调用目标方法
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//返回代理对象
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
@Override
public Object intercept(Invocation invocation) throws Throwable {
...
return invocation.proceed();
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);//调用下一个拦截器,最后一个的话则是目标类方法
}
前面提过mubatis可以拦截三种handler
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
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;
}
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,ResultSetHandler,ParameterHandler,对应sql执行拦截,执行结果拦截,参数拦截,另外还有一个可以拦截的点是Executor执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
...
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
所以注解中的签名@Signature的type为下面的四种
StatementHandler.class
ResultSetHandler.class
ParameterHandler.class
Executor.class
确定了type之后就可以配置method属性对type对象里面的方法进行拦截了
//拦截StatementHandler接口中的query(Statement, ResultHandler)方法
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
public interface StatementHandler {
...
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
...
}