MyBatis插件模块详解

MyBatis插件模块详解

一、MyBatis整体架构与插件模块

在深入插件模块之前,我们先了解MyBatis的整体架构,以及插件模块在其中的重要地位。

从上图可以看出,MyBatis采用了分层架构设计,而插件模块(Plugin)通过动态代理机制横切整个框架,能够在核心组件的执行过程中插入自定义逻辑。它为MyBatis提供了强大的扩展能力,使得开发者可以在不修改源码的情况下增强框架功能。

1.1 插件模块的核心职责

插件模块主要承担以下核心职责:

  1. 扩展框架功能:在不动源码的情况下增强MyBatis功能
  2. 拦截核心组件:拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler
  3. 实现AOP功能:通过动态代理实现面向切面编程
  4. 统一权限控制:实现数据权限过滤
  5. 性能监控:记录SQL执行时间和性能指标
  6. 分页查询:实现物理分页功能

1.2 为什么需要插件

在实际开发中,我们经常有这些需求:

sql 复制代码
需求场景:
├── SQL性能监控:记录慢查询
├── 数据权限控制:根据用户权限过滤数据
├── 分页查询:自动实现物理分页
├── 乐观锁:自动更新版本号
├── 审计日志:记录操作日志
└── 加密解密:敏感数据加密存储

如果没有插件机制,我们需要修改MyBatis源码或编写大量重复代码。插件机制让我们可以优雅地实现这些功能。

1.3 插件的实现原理

MyBatis的插件基于动态代理实现:

scss 复制代码
目标对象
    ↓
使用Plugin.wrap()包装
    ↓
生成代理对象
    ↓
调用intercept()方法
    ↓
执行自定义逻辑
    ↓
调用目标方法或继续传递

二、插件拦截机制

MyBatis的插件机制基于Java动态代理和责任链模式。

2.1 Interceptor接口

MyBatis插件的核心是Interceptor接口:

java 复制代码
public interface Interceptor {
    /**
     * 拦截方法,在这里实现自定义逻辑
     * @param invocation 代理调用对象
     * @return 方法执行结果
     * @throws Throwable 异常
     */
    Object intercept(Invocation invocation) throws Throwable;

    /**
     * 包装目标对象,生成代理对象
     * @param target 目标对象
     * @return 代理对象
     */
    default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 设置插件属性
     * @param properties 配置属性
     */
    default void setProperties(Properties properties) {
        // NOP
    }
}

2.2 Invocation类

Invocation封装了方法调用的相关信息:

java 复制代码
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);
    }

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

2.3 Plugin类

Plugin是代理对象的InvocationHandler:

java 复制代码
public class Plugin implements InvocationHandler {
    private final Object target;
    private final Interceptor interceptor;
    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) {
        // 获取拦截的类和方法签名
        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)) {
                // 执行拦截器的intercept方法
                return interceptor.intercept(new Invocation(target, method, args));
            }
            // 不需要拦截,直接执行
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }

    // 获取签名映射
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        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<?>[interfaces.size()]);
    }
}

三、四大核心拦截点

MyBatis允许拦截四个核心组件。

3.1 Executor(执行器)

Executor是MyBatis的核心执行器,负责SQL的执行。

可拦截方法:

  • update(MappedStatement ms, Object parameter) - 执行增删改
  • query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) - 查询
  • query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) - 查询(带缓存)
  • flushStatements() - 刷新批处理
  • commit() - 提交事务
  • rollback() - 回滚事务
  • getTransaction() - 获取事务
  • close() - 关闭会话
  • isClosed() - 是否关闭

示例:性能监控插件

java 复制代码
@Intercepts({
    @Signature(type = Executor.class,
               method = "update",
               args = {MappedStatement.class, Object.class}),
    @Signature(type = Executor.class,
               method = "query",
               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PerformanceMonitorPlugin implements Interceptor {
    private long threshold; // 慢查询阈值

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        long start = System.currentTimeMillis();

        try {
            // 执行目标方法
            return invocation.proceed();
        } finally {
            long end = System.currentTimeMillis();
            long time = end - start;

            if (time > threshold) {
                String sql = ms.getBoundSql(null).getSql();
                System.out.println("慢查询警告: " + ms.getId() + " 耗时: " + time + "ms");
                System.out.println("SQL: " + sql);
            }
        }
    }

    @Override
    public void setProperties(Properties properties) {
        this.threshold = Long.parseLong(properties.getProperty("threshold", "1000"));
    }
}

3.2 StatementHandler(语句处理器)

StatementHandler负责创建Statement对象并设置参数。

可拦截方法:

  • prepare(Connection connection) - 准备Statement
  • parameterize(Statement statement) - 设置参数
  • batch(Statement statement) - 批处理
  • update(Statement statement) - 执行更新
  • query(Statement statement, ResultHandler resultHandler) - 执行查询
  • getBoundSql() - 获取BoundSql
  • getParameterHandler() - 获取ParameterHandler
  • getResultHandler() - 获取ResultSetHandler

示例:SQL重写插件

java 复制代码
@Intercepts({
    @Signature(type = StatementHandler.class,
               method = "prepare",
               args = {Connection.class, Integer.class})
})
public class SQLRewritePlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();

        // 重写SQL:添加数据权限过滤
        if (sql.toLowerCase().startsWith("select")) {
            sql = rewriteSQL(sql);
            // 使用反射修改sql字段
            Field field = boundSql.getClass().getDeclaredField("sql");
            field.setAccessible(true);
            field.set(boundSql, sql);
        }

        return invocation.proceed();
    }

    private String rewriteSQL(String sql) {
        // 添加数据权限过滤条件
        return sql + " AND tenant_id = '" + getCurrentTenantId() + "'";
    }

    private String getCurrentTenantId() {
        // 获取当前租户ID
        return "1001";
    }
}

3.3 ParameterHandler(参数处理器)

ParameterHandler负责设置PreparedStatement的参数。

可拦截方法:

  • getParameterObject() - 获取参数对象
  • setParameters(PreparedStatement ps) - 设置参数

示例:参数加密插件

java 复制代码
@Intercepts({
    @Signature(type = ParameterHandler.class,
               method = "setParameters",
               args = {PreparedStatement.class})
})
public class ParameterEncryptPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ParameterHandler handler = (ParameterHandler) invocation.getTarget();
        PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];

        // 获取参数对象
        Object parameterObject = handler.getParameterObject();

        if (parameterObject instanceof User) {
            User user = (User) parameterObject;
            // 加密敏感字段
            if (user.getPassword() != null) {
                user.setPassword(encrypt(user.getPassword()));
            }
        }

        // 继续执行
        return invocation.proceed();
    }

    private String encrypt(String plainText) {
        // Base64简单加密
        return Base64.getEncoder().encodeToString(plainText.getBytes());
    }
}

3.4 ResultSetHandler(结果集处理器)

ResultSetHandler负责将ResultSet映射为Java对象。

可拦截方法:

  • handleResultSets(Statement stmt) - 处理结果集
  • handleOutputParameters(CallableStatement cs) - 处理存储过程输出参数

示例:结果解密插件

java 复制代码
@Intercepts({
    @Signature(type = ResultSetHandler.class,
               method = "handleResultSets",
               args = {Statement.class})
})
public class ResultDecryptPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 执行查询
        Object result = invocation.proceed();

        if (result instanceof List) {
            List<?> list = (List<?>) result;
            for (Object obj : list) {
                if (obj instanceof User) {
                    User user = (User) obj;
                    // 解密敏感字段
                    if (user.getIdCard() != null) {
                        user.setIdCard(decrypt(user.getIdCard()));
                    }
                }
            }
        }

        return result;
    }

    private String decrypt(String cipherText) {
        // Base64解密
        byte[] decoded = Base64.getDecoder().decode(cipherText);
        return new String(decoded);
    }
}

四、插件实现流程

理解插件的实现和配置流程非常重要。

4.1 实现自定义插件

步骤1:实现Interceptor接口

java 复制代码
@Intercepts({
    @Signature(type = Executor.class,
               method = "query",
               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class CustomPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 前置逻辑
        System.out.println("Before: " + invocation.getMethod().getName());

        // 执行目标方法
        Object result = invocation.proceed();

        // 后置逻辑
        System.out.println("After: " + invocation.getMethod().getName());

        return result;
    }

    @Override
    public Object plugin(Object target) {
        // 使用Plugin.wrap包装目标对象
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 读取配置属性
        String propertyName = properties.getProperty("propertyName");
        System.out.println("Property: " + propertyName);
    }
}

4.2 配置插件

方式1:XML配置

xml 复制代码
<configuration>
    <plugins>
        <!-- 性能监控插件 -->
        <plugin interceptor="com.example.plugin.PerformanceMonitorPlugin">
            <property name="threshold" value="1000"/>
        </plugin>

        <!-- 分页插件 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
        </plugin>

        <!-- 自定义插件 -->
        <plugin interceptor="com.example.plugin.CustomPlugin">
            <property name="propertyName" value="propertyValue"/>
        </plugin>
    </plugins>
</configuration>

方式2:代码配置

java 复制代码
// 创建Configuration
Configuration configuration = new Configuration();

// 添加插件
configuration.addInterceptor(new PerformanceMonitorPlugin());
configuration.addInterceptor(new PageInterceptor());
configuration.addInterceptor(new CustomPlugin());

// 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

方式3:Spring配置

java 复制代码
@Configuration
public class MyBatisConfig {

    @Bean
    public PerformanceMonitorPlugin performanceMonitorPlugin() {
        PerformanceMonitorPlugin plugin = new PerformanceMonitorPlugin();
        Properties properties = new Properties();
        properties.setProperty("threshold", "1000");
        plugin.setProperties(properties);
        return plugin;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);

        // 添加插件
        sessionFactory.setPlugins(
            new Interceptor[]{
                performanceMonitorPlugin(),
                new PageInterceptor()
            }
        );

        return sessionFactory.getObject();
    }
}

4.3 插件链的执行顺序

当配置了多个插件时,它们会形成责任链:

markdown 复制代码
原始Executor
    ↓
被Plugin1包装
    ↓
被Plugin2包装
    ↓
被Plugin3包装
    ↓
最终代理对象

执行顺序:

java 复制代码
// 配置顺序
<plugin interceptor="Plugin1"/>
<plugin interceptor="Plugin2"/>
<plugin interceptor="Plugin3"/>

// 执行顺序
Plugin3.intercept() → Plugin2.intercept() → Plugin1.intercept() → 目标方法

// 返回顺序
目标方法 → Plugin1处理 → Plugin2处理 → Plugin3处理 → 最终结果

五、动态代理链

多个插件会形成多层代理。

5.1 代理链的构建

java 复制代码
// 原始对象
Executor target = new SimpleExecutor();

// 第一次包装
Executor proxy1 = (Executor) Plugin.wrap(target, plugin1);

// 第二次包装(包装的是代理对象)
Executor proxy2 = (Executor) Plugin.wrap(proxy1, plugin2);

// 第三次包装
Executor proxy3 = (Executor) Plugin.wrap(proxy2, plugin3);

// proxy3是最外层的代理

5.2 代理链的执行

scss 复制代码
调用 proxy3.query()
    ↓
Plugin3.invoke()
    ↓
Plugin3.intercept()
    ↓
invocation.proceed() 调用 proxy2.query()
    ↓
Plugin2.invoke()
    ↓
Plugin2.intercept()
    ↓
invocation.proceed() 调用 proxy1.query()
    ↓
Plugin1.invoke()
    ↓
Plugin1.intercept()
    ↓
invocation.proceed() 调用 target.query()
    ↓
执行真正的查询
    ↓
返回结果
    ↓
Plugin1处理返回值
    ↓
Plugin2处理返回值
    ↓
Plugin3处理返回值
    ↓
最终返回

5.3 代理链示例代码

java 复制代码
@Intercepts(@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class Plugin1 implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("Plugin1 Before");
        Object result = invocation.proceed();
        System.out.println("Plugin1 After");
        return result;
    }
}

@Intercepts(@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class Plugin2 implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("Plugin2 Before");
        Object result = invocation.proceed();
        System.out.println("Plugin2 After");
        return result;
    }
}

// 输出结果:
// Plugin2 Before
// Plugin1 Before
// 执行查询
// Plugin1 After
// Plugin2 After

六、典型应用场景

插件在实际开发中有很多应用场景。

6.1 分页插件

PageHelper是最著名的MyBatis分页插件:

xml 复制代码
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.2</version>
</dependency>

配置:

xml 复制代码
<plugin interceptor="com.github.pagehelper.PageInterceptor">
    <property name="helperDialect" value="mysql"/>
    <property name="reasonable" value="true"/>
</plugin>

使用:

java 复制代码
// 查询前调用分页
PageHelper.startPage(1, 10);
List<User> users = userMapper.selectAll();

// 获取分页信息
PageInfo<User> pageInfo = new PageInfo<>(users);
System.out.println("总数: " + pageInfo.getTotal());
System.out.println("页数: " + pageInfo.getPages());

6.2 性能监控插件

java 复制代码
@Intercepts({
    @Signature(type = Executor.class,
               method = "update",
               args = {MappedStatement.class, Object.class}),
    @Signature(type = Executor.class,
               method = "query",
               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SlowQueryMonitorPlugin implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(SlowQueryMonitorPlugin.class);
    private long slowQueryThreshold;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        long start = System.currentTimeMillis();

        try {
            return invocation.proceed();
        } finally {
            long cost = System.currentTimeMillis() - start;
            if (cost > slowQueryThreshold) {
                BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]);
                String sql = boundSql.getSql();
                logger.warn("慢查询: {} 耗时: {}ms\nSQL: {}", ms.getId(), cost, sql);
            }
        }
    }

    @Override
    public void setProperties(Properties properties) {
        this.slowQueryThreshold = Long.parseLong(properties.getProperty("threshold", "1000"));
    }
}

6.3 数据权限插件

java 复制代码
@Intercepts({
    @Signature(type = StatementHandler.class,
               method = "prepare",
               args = {Connection.class, Integer.class})
})
public class DataPermissionPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();

        // 获取当前用户的数据权限
        Set<Long> deptIds = getCurrentUserDeptIds();

        // 重写SQL,添加数据权限过滤
        if (sql.toLowerCase().startsWith("select") && !deptIds.isEmpty()) {
            String condition = "dept_id IN (" + String.join(",", deptIds.toString()) + ")";
            sql = addDataPermission(sql, condition);

            // 使用反射修改SQL
            Field field = boundSql.getClass().getDeclaredField("sql");
            field.setAccessible(true);
            field.set(boundSql, sql);
        }

        return invocation.proceed();
    }

    private String addDataPermission(String sql, String condition) {
        // 简单实现:在WHERE后添加条件
        if (sql.toLowerCase().contains("where")) {
            return sql + " AND " + condition;
        } else {
            return sql + " WHERE " + condition;
        }
    }

    private Set<Long> getCurrentUserDeptIds() {
        // 从上下文获取当前用户的数据权限
        return SecurityContextHolder.getCurrentUserDataPermission();
    }
}

6.4 乐观锁插件

java 复制代码
@Intercepts({
    @Signature(type = Executor.class,
               method = "update",
               args = {MappedStatement.class, Object.class})
})
public class OptimisticLockPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object parameter = invocation.getArgs()[1];

        if (parameter instanceof BaseEntity) {
            BaseEntity entity = (BaseEntity) parameter;
            // 自动设置版本号
            if (entity.getVersion() == null) {
                entity.setVersion(0);
            } else {
                entity.setVersion(entity.getVersion() + 1);
            }
        }

        return invocation.proceed();
    }
}

6.5 审计日志插件

java 复制代码
@Intercepts({
    @Signature(type = Executor.class,
               method = "update",
               args = {MappedStatement.class, Object.class})
})
public class AuditLogPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];

        // 记录操作日志
        String operation = ms.getId();
        String operator = getCurrentUser();
        Date operateTime = new Date();

        System.out.println("操作人: " + operator);
        System.out.println("操作时间: " + operateTime);
        System.out.println("操作类型: " + operation);
        System.out.println("操作数据: " + parameter);

        // 执行目标方法
        Object result = invocation.proceed();

        // 记录操作结果
        System.out.println("影响行数: " + result);

        return result;
    }

    private String getCurrentUser() {
        // 获取当前登录用户
        return SecurityContextHolder.getCurrentUser().getUsername();
    }
}

七、最佳实践

7.1 插件设计原则

  1. 最小侵入:尽量不修改原有逻辑
  2. 可配置性:通过属性配置开关
  3. 性能考虑:避免在插件中执行耗时操作
  4. 异常处理:妥善处理异常,避免影响正常流程
  5. 日志记录:记录关键操作日志

7.2 性能优化

  1. 减少反射使用:缓存Field/Method对象
  2. 避免复杂计算:插件逻辑要简单高效
  3. 使用缓存:缓存常用数据
  4. 异步处理:日志等操作异步执行

7.3 常见问题解决

问题1:插件不生效
java 复制代码
// 原因:@Signature配置错误
// 错误:方法签名不匹配
@Signature(type = Executor.class,
           method = "query",
           args = {MappedStatement.class, Object.class}) // 缺少参数

// 正确:完整的方法签名
@Signature(type = Executor.class,
           method = "query",
           args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
问题2:修改SQL失败
java 复制代码
// 原因:直接修改sql字段不生效
boundSql.setSql(newSql); // BoundSql没有setSql方法

// 正确:使用反射修改
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, newSql);
问题3:插件顺序混乱
xml 复制代码
<!-- 建议:按执行顺序配置 -->
<!-- 分页插件应该先执行 -->
<plugin interceptor="PageInterceptor"/>

<!-- 数据权限插件后执行 -->
<plugin interceptor="DataPermissionPlugin"/>

7.4 插件开发模板

java 复制代码
/**
 * 插件开发模板
 */
@Intercepts({
    @Signature(type = Executor.class,
               method = "query",
               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class TemplatePlugin implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(TemplatePlugin.class);

    // 配置属性
    private String configProperty;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 前置处理
        Object target = invocation.getTarget();
        Method method = invocation.getMethod();
        Object[] args = invocation.getArgs();

        logger.debug("Before intercept: {}", method.getName());

        try {
            // 2. 执行目标方法
            Object result = invocation.proceed();

            // 3. 后置处理
            logger.debug("After intercept: {}", method.getName());

            return result;
        } catch (Exception e) {
            // 4. 异常处理
            logger.error("Plugin error", e);
            throw e;
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.configProperty = properties.getProperty("configProperty", "defaultValue");
        logger.info("Plugin initialized with property: {}", configProperty);
    }
}

八、总结

MyBatis的插件模块提供了强大的扩展能力,使得开发者可以在不修改源码的情况下增强框架功能。

核心要点

  1. Interceptor接口:定义插件的核心接口
  2. 四大拦截点:Executor、StatementHandler、ParameterHandler、ResultSetHandler
  3. 动态代理:基于JDK动态代理实现
  4. 责任链模式:多个插件形成责任链
  5. 应用场景:分页、监控、权限、加密、日志等
相关推荐
CodeAmaz13 小时前
MyBatis 分页插件实现原理(Interceptor 机制 + SQL 改写)
mybatis·分页插件
CodeAmaz14 小时前
MyBatis 如何实现“面向接口”查询
mybatis·面向接口
此剑之势丶愈斩愈烈15 小时前
mybatis-plus乐观锁
开发语言·python·mybatis
雨中飘荡的记忆16 小时前
MyBatis数据源模块详解
mybatis
heartbeat..16 小时前
Java 持久层框架 MyBatis 全面详解(附带Idea添加对应的XML文件模板教程)
java·数据库·intellij-idea·mybatis·持久化
Predestination王瀞潞17 小时前
Java EE数据访问框架技术(第三章:Mybatis多表关系映射-下)
java·java-ee·mybatis
刘一说1 天前
2026年Java技术栈全景图:从Web容器到云原生的深度选型指南(附避坑指南)
java·前端·spring boot·后端·云原生·tomcat·mybatis
独自归家的兔2 天前
Spring Retryable 注解完全指南:从入门到精通,让接口容错更简单
java·spring·mybatis
sunddy_x2 天前
MyBatis 与 SpringBoot 集成入门
spring boot·后端·mybatis