Mybatis拦截器原理解析

一、概述

在Mybatis开发体系中,“过滤器”是开发者对Mybatis拦截器(Plugin)的通俗称呼,它是Mybatis提供的核心扩展机制——无需修改框架源码,就能对SQL执行全流程进行增强,比如分页插件、通用字段自动填充、数据加解密等功能,底层均依赖这一机制。本文将从底层技术原理、核心源码解析到实战开发,完整拆解Mybatis过滤器的实现逻辑,还原其设计精髓。

二、核心对象

2.1 可拦截的核心接口

MyBatis仅允许拦截其内部4个核心接口的方法,这4个接口覆盖了SQL执行的全流程,开发者需通过@Intercepts注解指定拦截目标:

拦截接口 核心作用 常用拦截方法 典型应用场景
Executor SQL执行器(增删改查入口) query/update/commit 耗时统计、分页插件、数据权限控制
ParameterHandler 参数处理器(设置SQL预编译参数) setParameters 参数加密、参数校验、默认值填充
ResultSetHandler 结果集处理器(封装查询结果) handleResultSets 结果集解密、数据脱敏、格式转换
StatementHandler SQL语句处理器(创建Statement对象) prepare/parameterize SQL改写、SQL日志打印、防注入处理

2.2 拦截器核心接口

自定义拦截器必须实现org.apache.ibatis.plugin.Interceptor接口,该接口包含3个核心方法:

方法名 作用说明
intercept(Invocation invocation) 核心拦截逻辑入口,SQL执行时触发,可修改参数、增强结果、统计信息等
plugin(Object target) 为目标对象创建动态代理,推荐使用MyBatis提供的Plugin.wrap()方法实现
setProperties(Properties properties) 读取拦截器配置属性(如从配置文件传入的密钥、日志级别等),可选实现

2.3 关键注解

  1. @Intercepts:标识该类为MyBatis拦截器,内部可包含多个@Signature注解,指定拦截的接口、方法和参数。
  2. @Signature:定义具体的拦截规则,包含3个属性:
    • type:拦截的核心接口(如Executor.class);
    • method:拦截的方法名(如query);
    • args:拦截方法的参数类型数组(必须与接口方法参数完全匹配)。

三、工作流程

MyBatis拦截器的执行遵循“动态代理+责任链”模式,完整流程如下:

  1. 初始化阶段:Spring Boot启动时,通过配置类将自定义拦截器注册到SqlSessionFactory
  2. 代理创建阶段:MyBatis初始化核心接口(如Executor)时,调用拦截器的plugin()方法,为目标接口创建动态代理;
  3. 方法拦截阶段:执行Mapper方法触发SQL时,代理对象拦截目标方法调用,进入intercept()方法;
  4. 自定义逻辑执行:在intercept()中执行前置增强(如记录开始时间、修改参数);
  5. 原方法执行:通过invocation.proceed()调用被拦截的原方法,执行SQL操作;
  6. 后置处理阶段:原方法执行完成后,执行后置增强(如统计耗时、解密结果集);
  7. 结果返回:将处理后的结果返回给调用者。

若注册了多个拦截器,将按“注册顺序逆序执行”(责任链模式),例如注册顺序为拦截器A→拦截器B→拦截器C,实际执行顺序为拦截器C→拦截器B→拦截器A

四、底层实现

Mybatis过滤器的实现依赖两大基础技术,我们先通过极简示例还原核心逻辑,再对接Mybatis源码。

4.1 基础:JDK动态代理核心实现

JDK动态代理是过滤器的“增强载体”,通过InvocationHandler对目标对象方法进行前置/后置增强,先看基础源码实现:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 目标接口(模拟Mybatis核心接口)
public interface Executor {
    void query(String sql);
}

// 目标实现类(模拟Mybatis默认Executor)
public class SimpleExecutor implements Executor {
    @Override
    public void query(String sql) {
        System.out.println("执行SQL:" + sql);
    }
}

// 动态代理处理器(核心增强逻辑)
public class PluginInvocationHandler implements InvocationHandler {
    // 目标对象(如Executor实例)
    private final Object target;
    // 拦截器(过滤器)实例
    private final Interceptor interceptor;

    public PluginInvocationHandler(Object target, Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 执行过滤器的拦截逻辑
        Object result = interceptor.intercept(new Invocation(target, method, args));
        return result;
    }

    // 生成代理对象的工具方法
    public static Object wrap(Object target, Interceptor interceptor) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new PluginInvocationHandler(target, interceptor)
        );
    }
}

// 抽象过滤器(拦截器)接口
public interface Interceptor {
    // 核心拦截方法
    Object intercept(Invocation invocation) throws Throwable;

    // 为目标对象创建代理(绑定过滤器)
    default Object plugin(Object target) {
        return PluginInvocationHandler.wrap(target, this);
    }
}

// 方法调用封装类(封装目标对象、方法、参数)
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 Throwable {
        return method.invoke(target, args);
    }

    // getter方法
    public Object getTarget() { return target; }
    public Method getMethod() { return method; }
    public Object[] getArgs() { return args; }
}

4.2 进阶:责任链模式管理多过滤器

当存在多个过滤器时,需要通过“拦截器链”管理执行顺序,这是责任链模式的典型应用,源码如下:

import java.util.ArrayList;
import java.util.List;

// 过滤器链(拦截器链)
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 new ArrayList<>(interceptors);
    }
}

// 测试:多过滤器链式执行
public class TestProxy {
    public static void main(String[] args) throws Throwable {
        // 1. 创建目标对象
        Executor target = new SimpleExecutor();
        // 2. 创建过滤器链并注册过滤器
        InterceptorChain chain = new InterceptorChain();
        // 过滤器1:日志增强
        chain.addInterceptor(new Interceptor() {
            @Override
            public Object intercept(Invocation invocation) throws Throwable {
                System.out.println("【日志过滤器】前置增强:执行SQL前打印日志");
                Object result = invocation.proceed();
                System.out.println("【日志过滤器】后置增强:执行SQL后打印日志");
                return result;
            }
        });
        // 过滤器2:性能监控
        chain.addInterceptor(new Interceptor() {
            @Override
            public Object intercept(Invocation invocation) throws Throwable {
                long start = System.currentTimeMillis();
                Object result = invocation.proceed();
                long end = System.currentTimeMillis();
                System.out.println("【性能过滤器】SQL执行耗时:" + (end - start) + "ms");
                return result;
            }
        });
        // 3. 绑定所有过滤器,生成代理对象
        Executor proxyExecutor = (Executor) chain.pluginAll(target);
        // 4. 执行目标方法
        proxyExecutor.query("SELECT * FROM user WHERE id = 1");
    }
}

执行结果:

【日志过滤器】前置增强:执行SQL前打印日志
【性能过滤器】前置增强:开始计时
执行SQL:SELECT * FROM user WHERE id = 1
【性能过滤器】后置增强:SQL执行耗时:0ms
【日志过滤器】后置增强:执行SQL后打印日志

上述示例完整还原了Mybatis过滤器的核心逻辑:通过JDK动态代理实现单方法增强,通过责任链模式实现多过滤器的链式执行。

五、核心源码解析

Mybatis源码中对过滤器的实现与上述示例高度一致,以下是核心类的源码拆解:

5.1 核心接口:Interceptor

Mybatis定义的过滤器核心接口,所有自定义过滤器必须实现此接口:

package org.apache.ibatis.plugin;

import java.util.Properties;

public interface Interceptor {
    // 核心拦截方法:编写自定义增强逻辑
    Object intercept(Invocation invocation) throws Throwable;

    // 为目标对象创建代理对象
    default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    // 设置过滤器配置属性(从mybatis-config.xml读取)
    default void setProperties(Properties properties) {
        // 空实现,可自定义覆盖
    }
}

5.2 代理核心类:Plugin

Mybatis内置的InvocationHandler实现类,负责动态代理的创建和方法拦截判断:

package org.apache.ibatis.plugin;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Plugin implements InvocationHandler {

    // 目标对象(如Executor、StatementHandler实例)
    private final Object target;

    // 当前过滤器实例
    private final Interceptor interceptor;

    // 过滤器注解配置的拦截方法映射(@Intercepts + @Signature)
    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注解,获取需要拦截的方法
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        // 获取目标对象的所有接口(仅拦截Mybatis四大核心接口)
        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);
        }
    }

    // 解析@Intercepts注解,生成拦截方法映射
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        // 无@Intercepts注解则抛出异常
        if (interceptsAnnotation == null) {
            throw new PluginException("No @Intercepts annotation was found in interceptor "
                                      + interceptor.getClass().getName());
        }
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
        for (Signature sig : sigs) {
            Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
            try {
                Method method = sig.type().getMethod(sig.method(), sig.args());
                methods.add(method);
            } catch (NoSuchMethodException e) {
                throw new PluginException("Could not find method on " + sig.type()
                                         + " named " + sig.method() + ". Cause: " + e, e);
            }
        }
        return signatureMap;
    }

    // 获取目标对象的所有可拦截接口
    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        Set<Class<?>> interfaces = new HashSet<>();
        while (type != null) {
            for (Class<?> c : type.getInterfaces()) {
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
            type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[0]);
    }
}

5.3 过滤器链:InterceptorChain

Mybatis用于管理所有注册过滤器的核心类,负责为目标对象绑定所有过滤器:

package org.apache.ibatis.plugin;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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);
    }
}

5.4 初始化入口:Configuration

Mybatis在创建四大核心接口实例时,会通过InterceptorChain绑定所有过滤器,以Executor为例:

package org.apache.ibatis.session;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.executor.ReuseExecutor;
import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.plugin.InterceptorChain;

public class Configuration {
    // 过滤器链实例
    protected final InterceptorChain interceptorChain = new InterceptorChain();

    // 创建Executor实例并绑定过滤器
    public Executor newExecutor(Transaction transaction) {
        return newExecutor(transaction, defaultExecutorType);
    }

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : 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);
        }
        // 开启缓存则包装为CachingExecutor
        if (cacheEnabled) {
            executor = new CachingExecutor(executor);
        }
        // 为Executor绑定所有过滤器(核心步骤)
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }

    // ParameterHandler/StatementHandler/ResultSetHandler创建逻辑同理
    // ...
}

六、使用注意事项

  • 拦截范围限制:仅能拦截ExecutorParameterHandlerStatementHandlerResultSetHandler四大接口的方法,非核心接口无法拦截;
  • 注解配置规范:@Signature中的method必须与目标接口方法名一致,args必须与方法参数类型完全匹配(顺序、类型均不能错);
  • 性能影响:每一次拦截都会触发动态代理的invoke方法,过多过滤器会增加方法调用层级,需按需开发;
  • 线程安全:过滤器实例为单例,若过滤器中包含成员变量,需保证线程安全;
  • 避免循环拦截:在拦截方法中调用目标对象方法时,避免再次触发自身拦截逻辑。

七、总结

Mybatis过滤器(拦截器)是JDK动态代理与责任链模式的经典应用,其核心设计思路可总结为:

  • 精准拦截:通过@Intercepts+ @Signature注解精准定位需要增强的核心接口和方法;
  • 链式增强:通过InterceptorChain管理多个过滤器,按注册顺序依次执行增强逻辑;
  • 低侵入扩展:无需修改Mybatis源码,通过实现Interceptor接口即可扩展核心功能。

掌握过滤器原理后,不仅能灵活实现分页、字段填充、数据加解密等常见需求,还能基于此定制化扩展Mybatis的核心能力,是Mybatis进阶开发的必备知识点。

posted @ 2025-12-08 21:18  夏尔_717  阅读(0)  评论(0)    收藏  举报