由 Mybatis 源码畅谈软件设计(六):Interceptor 拦截器的设计

170 阅读5分钟

本节我们来介绍 Mybatis 的拦截器 Interceptor,它依靠 @Intercepts@Signature 注解驱动,配置拦截器的切入方法,这种声明方式非常直观,能够准确的知道每个拦截器的作用范围。而且它是非侵入性的,采用了 动态代理模式,在不修改原有逻辑的前提下便能实现功能的扩展,遵循 开闭原则。Spring 框架中的 AOP 也采用的是同样的思想,但是它引入了很多概念(切面、连接点、切入点和通知等等),代码量超过 5000 行,而 Mybatis Interceptor 的实现仅有 370 行,非常精简,接下来我们先介绍下它的原理。

首先是解析 mybatis-config.xml 配置文件中定义的拦截器方法 Configuration#pluginsElement,解析创建完成后会被保存在 InterceptorChain interceptorChain 对象中:

public class Configuration {
    // ...
    protected final InterceptorChain interceptorChain = new InterceptorChain();

    private void pluginsElement(XNode context) throws Exception {
        if (context != null) {
            for (XNode child : context.getChildren()) {
                // 获取每个节点下的拦截器配置
                String interceptor = child.getStringAttribute("interceptor");
                // 获取拦截器参数配置增加灵活性
                Properties properties = child.getChildrenAsProperties();
                // 反射创建拦截器实例
                Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor()
                        .newInstance();
                interceptorInstance.setProperties(properties);
                // 注册拦截器
                configuration.addInterceptor(interceptorInstance);
            }
        }
    }

    public void addInterceptor(Interceptor interceptor) {
        interceptorChain.addInterceptor(interceptor);
    }
}

那这些拦截器在哪里生效呢?其实在前面我们讲 SQL 执行流程时,已经见过它的身影,仍然是在 Configuration 中,创建四大处理器时都能看到 pluginAll 方法的身影(从这里也能知道拦截器的作用范围便是这四大拦截器):

public class Configuration {
    // ...
    protected final InterceptorChain interceptorChain = new InterceptorChain();
    
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
                                                BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,
                parameterObject, boundSql);
        // 拦截器相关逻辑
        return (ParameterHandler) interceptorChain.pluginAll(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);
        // 拦截器相关逻辑
        return (ResultSetHandler) interceptorChain.pluginAll(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);
        // 拦截器相关逻辑
        return (StatementHandler) interceptorChain.pluginAll(statementHandler);
    }

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        // 创建具体的 Executor 实现类
        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);
        }
        // 拦截器相关逻辑
        return (Executor) interceptorChain.pluginAll(executor);
    }

}

在每个处理器创建完成后都会执行 pluginAll 方法,接着我们看一下 InterceptorChain#pluginAll 方法:

public class InterceptorChain {

    private final List<Interceptor> interceptors = new ArrayList<>();

    public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
            // 注意 target 引用不断变化,会不断引用已经添加拦截器的对象
            target = interceptor.plugin(target);
        }
        return target;
    }

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

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

}

InterceptorChain 实现非常简单,内部定义了集合来保存所有配置的拦截器,执行 pluginAll 方法时会遍历该集合,逐个调用 Interceptor#plugin 方法来不断添加拦截器。注意这里使用到了 责任链模式,由 InterceptorChain 的命名中包含 Chain 也能联想到该模式,之后我们在使用责任链时也可以考虑在命名中增加 Chain 以增加可读性。InterceptorChain 将多个拦截器串联在一起,每个拦截器负责其特定的逻辑处理,并在执行完自己的逻辑后,调用下一个拦截器或目标方法,这样设计允许不同的拦截器之间的逻辑 解耦,同时提供了 可扩展性

接着我们再看 Interceptor#plugin 方法:

public interface Interceptor {

    Object intercept(Invocation invocation) throws Throwable;

    default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    default void setProperties(Properties properties) {
        // NOP
    }

}

它是定义在接口中的 default 方法,在实际业务开发中还是较少看见这个关键字的。继续探究 Plugin#wrap 方法:

public class Plugin implements InvocationHandler {

    // 被拦截器代理对象或被代理的拦截器
    private final Object target;
    // 代理拦截器本身
    private final Interceptor interceptor;
    // 要被拦截的方法 Map
    private final Map<Class<?>, Set<Method>> signatureMap;

    private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }

    public static Object wrap(Object target, Interceptor interceptor) {
        // 根据 Intercepts 注解和 Signature 注解获取要被拦截的方法
        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;
    }
    
    @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));
            }
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }
    
    // ...
}

Plugin 实现了 InvocationHandler 接口,使用了 动态代理模式,是拦截器实现的核心类。在 wrap 方法中,会根据 @Intercepts@Signature 来定位到要被拦截的方法,因为这部分逻辑很简单,便不再赘述了,后续便会创建 Plugin 代理对象。在 invoke 方法中,它会判断要执行的方法是否为被拦截的方法,并执行对应的逻辑,在执行拦截器逻辑时,它并没有直接将 methodtargetargs 参数直接暴露出去供拦截器做逻辑,而是将它们 封装 起来为 Invocation 对象:

public class Invocation {

    private static final List<Class<?>> targetClasses = Arrays.asList(Executor.class, ParameterHandler.class,
            ResultSetHandler.class, StatementHandler.class);

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

    public Invocation(Object target, Method method, Object[] args) {
        if (!targetClasses.contains(method.getDeclaringClass())) {
            throw new IllegalArgumentException("Method '" + method + "' is not supported as a plugin target.");
        }
        this.target = target;
        this.method = method;
        this.args = args;
    }

    public Object getTarget() {
        return target;
    }

    public Method getMethod() {
        return method;
    }

    public Object[] getArgs() {
        return args;
    }

    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }

}

Invocation 提供了 Get 方法来获取这些元素,并没有将它们全部都隐藏起来,而且也提供了方便的 process 方法调用,在构造方法中还添加了对应的校验,降低了 复杂度,这样在实现拦截器的大部分情况下,只需执行 process 方法即可,简单易用。

到现在为止拦截器的实现原理已经介绍完了,我们来看一下拦截器使用的例子,定义两个拦截器,以 org.apache.ibatis.plugin.PluginTest 为例:

class PluginTest {
    
    // 更换库名
    @Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
    public static class SwitchCatalogInterceptor implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            Object[] args = invocation.getArgs();
            Connection con = (Connection) args[0];
            con.setSchema(SchemaHolder.get());
            return invocation.proceed();
        }
    }
    
    // 打印执行前、后的日志
    @Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
    public class LogInterceptor implements Interceptor {
        
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("Before Process...");
            Object res = invocation.proceed();
            System.out.println("After Process...");
            return res;
        }
    }
}

mybatis-config.xml 中定义的拦截器顺序如下,在向 interceptorChain 中添加时 SwitchCatalogInterceptor 在前,LogInterceptor 在后:

<configuration>
    <!--    ...-->
    <plugins>
        <plugin interceptor="org.apache.ibatis.plugin.PluginTest$SwitchCatalogInterceptor" />
        <plugin interceptor="org.apache.ibatis.plugin.PluginTest$LogInterceptor" />
    </plugins>

</configuration>

那么在应用拦截器时 越定义在后面的拦截器越会先生效,如下所示,LogInterceptorSwitchCatalogInterceptor 外层:

interceptor_example.png

如果要考虑拦截器的执行顺序,那么便需要在添加拦截器时多注意了。