Mybatis:拦截器原理

简要阐述一下Mybatis执行数据库查询操作过程,使用Executor作为执行器调用StatementHandlerStatement进行处理,如果是PreparedStatement会通过PreparedStatementHandler为条件占位符设置条件值,底层还是通过JDBC原生Statement执行数据库操作,通过ResultSetHandler返回处理返回的ResultSet结果集封装成ResultMap对象返回

Mybatis架构

简单了解了Mybatis执行的主要流程,明白StatementHandlerParameterHandlerResultSetHandler的角色定位,铺垫了学习拦截器使用场景和原理的基础

在Mybatis的核心配置类Configuration中,可以看到在创建ParameterHandlerResultSetHandlerStatementHandler时为它们进行了拦截操作

public class Configuration {
    // 拦截器注册中心
    protected final InterceptorChain interceptorChain = new InterceptorChain();
    /*
    * 创建ParameterHandler
    */
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, 
            Object parameterObject, BoundSql boundSql) {

        ParameterHandler parameterHandler = mappedStatement.getLang()
                .createParameterHandler(mappedStatement, parameterObject, boundSql);
        // 为ParameterHandler应用拦截器
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
    }
    /*
    * 创建ResultSetHandler 
    */
    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 = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
    }
    /*
    * 创建StatementHandler 
    */
    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 = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }
}

InterceptorChain顾名思义为拦截器链,也可以简单地理解为拦截器的注册中心,Mybatis所有拦截器都注册到了拦截器链中

public class InterceptorChain {

    private final List<Interceptor> interceptors = new ArrayList<>();
    // 每个拦截器都会尝试拦截所有的处理器
    public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
        }
        return target;
  }

    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }
}

Interceptor是拦截器接口,重点提供了一个调用拦截器的plugin方法,以及拦截器拦截逻辑处理intercept方法

public interface Interceptor {
    // 应用拦截器逻辑
    Object intercept(Invocation invocation) throws Throwable;
    // 执行当前拦截器
    Object plugin(Object target);
    void setProperties(Properties properties);
}

以最常用的分页处理插件,分析如何应用拦截器

PaginationInterceptor拦截StatementHandlerprepare方法执行,该方法入参为ConnectionInteger

它的核心原理是为StatementHandler生成代理对象,当StatementHandler执行prepare方法的时候,执行拦截方法添加分页逻辑

当通过Configuration创建StatementHandler的时候,会触发拦截器链的执行,PaginationInterceptorplugin方法会被调用,此时它为StatementHandler生成代理对象,当代理对象执行prepare方法的时候会触发代理对象的invoke方法,此时会校验当前执行的方法是否为拦截方法,若是则执行PaginationInterceptor的分页逻辑interceptor方法

@Intercepts({
    @Signature(
        type = StatementHandler.class, 
        method = "prepare", 
        args = {Connection.class, Integer.class})})
public class PaginationInterceptor extends SqlParserHandler implements Interceptor {

    /**
     * 这个方法会被Mybatis的拦截器链调用
     * 在这里为StatementHandler生成代理对象
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    // 分页拦截器添加分页逻辑拦截方法
    public Object intercept(Invocation invocation) throws Throwable {
        // ...
        return invocation.proceed();  // 处理完成调用被代理对象的目标方法,放行
    }

}

Plugin为拦截器提供了工具方法,可以为拦截目标生成代理对象,记录拦截目标被拦截的方法信息,以及作为InvocationHandler实现类提供了代理增强方法invoke

public class Plugin implements InvocationHandler {
    /**
     * @param target 被拦截的对象
     * @param interceptor 拦截器
     */
    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) {
            // 利用JDK动态代理生成代理对象
            return Proxy.newProxyInstance(
                    type.getClassLoader(),
                    interfaces,
                    // 当前类实现了动态代理InvocationHandler接口,
                    // 封装被代理的目标,拦截器,以及拦截的目标方法
                    new Plugin(target, interceptor, signatureMap));  
        }
        return target;
    }

    /*
     * 解析拦截器类上的Intercepts注解,缓存要拦截的方法信息
     */
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        if (interceptsAnnotation == null) {
            // ...省略异常输出
        }
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
        for (Signature sig : sigs) {
            Set<Method> methods = signatureMap.get(sig.type());
            if (methods == null) {
                methods = new HashSet<Method>();
                signatureMap.put(sig.type(), methods);
            }
            try {
                Method method = sig.type().getMethod(sig.method(), sig.args());
                methods.add(method);
                } catch (NoSuchMethodException e) {
                    // ...
            }
        }
        return signatureMap;
    }  

    /**
     * 这个方法是JDK动态代理对象调用目标方法之后,会被调用的方法
     * 在这里首先判断当前当前代理对象执行的方法是否为拦截方法,
     * 如果不是使用被代理对象调用原先方法
     * 如果是则使用调用代理对象的拦截方法
     */ 
    @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)) {
                // 注意这里会通过Invocation封装被代理对象、调用方法和调用参数传递给拦截器
                return interceptor.intercept(new Invocation(target, method, args));
            }
            // 调用的不是拦截方法,使用被代理对象调用原先方法
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }
}

Invocation封装了被代理的目标对象和被调用的方法,作为入参传递给拦截器的interceptor方法,当拦截器执行完逻辑之后,可以通过proceed方法调用被代理对象的原方法逻辑

public class Invocation {

    private final Object target;
    private final Method method;
    private final Object[] args;

    public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }
    // ...
    // 调用被代理对象的目标方法,结束拦截器的使用
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。