技术博客 技术博客
  • JAVA
  • 仓颉
  • 设计模式
  • 人工智能
  • Spring
  • Mybatis
  • Maven
  • Git
  • Kafka
  • RabbitMQ
  • RocketMQ
  • Redis
  • Zookeeper
  • Nginx
  • 数据库套件
  • MySQL
  • Elasticsearch
  • MongoDB
  • Hadoop
  • ClickHouse
  • Hbase
  • Hive
  • Flink
  • Flume
  • SQLite
  • linux
  • Docker
  • Jenkins
  • Kubernetes
  • 工具
  • 前端
  • AI
GitHub (opens new window)
  • JAVA
  • 仓颉
  • 设计模式
  • 人工智能
  • Spring
  • Mybatis
  • Maven
  • Git
  • Kafka
  • RabbitMQ
  • RocketMQ
  • Redis
  • Zookeeper
  • Nginx
  • 数据库套件
  • MySQL
  • Elasticsearch
  • MongoDB
  • Hadoop
  • ClickHouse
  • Hbase
  • Hive
  • Flink
  • Flume
  • SQLite
  • linux
  • Docker
  • Jenkins
  • Kubernetes
  • 工具
  • 前端
  • AI
GitHub (opens new window)
  • Spring

    • spring

      • 核心内容拆解 IOC
      • 核心内容拆解 AOP
      • 核心内容拆解 事件通知
      • 核心内容拆解 三级缓存
      • 核心内容拆解 FactoryBean
      • 注解替代Spring生命周期实现类
    • spring mv

      • Spring MVC 之基本工作原理
    • spring boot

      • SpringBoot 之 Filter、Interceptor、Aspect
      • SpringBoot 之 Starter
      • SpringBoot 之 Stomp 使用和 vue 相配置
      • SpringBoot MyBatisPlus 实现多数据源
      • SpringBoot MyBatis 动态建表
      • Spring Boot 集成 Jasypt 3.0.3 配置文件加密
      • Spring Boot 集成 FastDFS
      • Spring Boot VUE前后端加解密
      • Spring Boot logback.xml 配置
      • Spring Boot MinIO
      • Spring Boot kafka
      • Spring Boot WebSocket
    • spring cloud

      • SpringCloud - Ribbon和Feign
      • SpringCloud alibaba - Nacos
      • SpringCloud alibaba - Sentinel哨兵
      • SpringCloud alibaba - Gateway
      • SpringCloud alibaba - 链路跟踪
      • SpringCloud - 分布式事务一(XA,2PC,3PC)
      • SpringCloud - 分布式事务二(Seata-AT,TCC,Saga)
      • SpringCloud - 分布式事务三(Seata搭建)
      • SpringCloud - 分布式事务四(多数据源事务)
      • SpringCloud - 分布式事务五(微服务间调用的事务处理)
  • Mybatis

    • 核心功能拆解 工作流程
    • 核心功能拆解 Plugin插件功能实现
      • 解析
      • 注册
      • 执行
      • 自定义
    • 核心功能拆解 一二级缓存原理
    • MyBatis Plus+Spring Boot 实现一二级缓存以及自定义缓存
  • maven

    • pom 文件介绍及 parent、properties 标签详解
    • dependencies 标签详解
    • 使用 Nexus3.x 搭建私服
  • git

    • 私有 git 仓库搭建
目录

核心功能拆解 Plugin插件功能实现

Mybatis Plugin 是随着 Mybatis 的工作流程一起被进行 解析->注册->执行 的,了解每个步骤才能更好的对 Mybatis 所提供的 Plugin 机制进行实现和扩展。在我们已知的 Mybatis 的插件有分页插件、缓存插件等,之所以可以能做到扩展是因为他在自己本身的 Executor(生产执行器) , StatementHandler(语句处理器) , ParameterHandler(参数处理器) , ResultSetHandler(结果集处理器) 这四个地方做了拦截,当介绍到执行步骤的时候就可以看到具体实现。

# 解析

常规的 XML 配置

<plugins>
    <plugin interceptor="cn.mybatis.test.plugin.TestPlugin">
        <property name="test00" value="100"/>
        <property name="test01" value="200"/>
    </plugin>
</plugins>
1
2
3
4
5
6

核心解析方法

private void pluginElement(Element parent) throws Exception {
    if (parent == null) return;
    List<Element> elements = parent.elements();
    for (Element element : elements) {
        // 解析类路径
        String interceptor = element.attributeValue("interceptor");
        // 参数配置
        Properties properties = new Properties();
        List<Element> propertyElementList = element.elements("property");
        for (Element property : propertyElementList) {
            properties.setProperty(property.attributeValue("name"), property.attributeValue("value"));
        }
        // 获取插件实现类并实例化:cn.mybatis.test.plugin.TestPlugin 
        // 通过 Resources.classForName(string) 获取实例
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        // 设置配置属性
        interceptorInstance.setProperties(properties);
        // 注册到 configuration 中
        configuration.addInterceptor(interceptorInstance);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 注册

解析的时候会在方法内部执行 configuration.addInterceptor(interceptorInstance); 这一步是把插件维护到 Configuration 全局配置中,但插件其实应该有很多各,所以提供的是一个 InterceptorChain 对象由 Configuration 维护

public class Configuration {
    // 插件拦截器链
    protected final InterceptorChain interceptorChain = new InterceptorChain();
    public void addInterceptor(Interceptor interceptorInstance) {
        interceptorChain.addInterceptor(interceptorInstance);
    }
    // other 其他配置
}
1
2
3
4
5
6
7
8

InterceptorChain 里面维护的是一个集合,专门存放所有的 Plugin

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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

从解析到注册这两步就可以看出,MyBatis 是把插件以拦截器的形式存放到一个拦截器容器里,这个容器是 Configuration 全局配置类来进行维护的

# 执行

执行是在调用具体的查询方法活其他在 Mybatis 里所描述的 SQL 方法时进行的触发,触发会在如下代码中的位置中触发。

  // 创建参数处理器
  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;
  }

  // 生产执行器
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    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);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

会看到他会调用 InterceptorChain#pluginAll 方法,该类在注册步骤中有提及到,里面维护了所有的插件,那么在这里就会时循环所有的插件,每个插件调用 Interceptor#plugin

// 循环调用
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

// 执行wrap
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
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Interceptor#plugin 方法内部也就是调用 Plugin#wrap 静态方法,该方法通过获取自定义插件的注解,来观察你需要对哪个处理器,哪个方法以及参数类型去匹配拦截对象的具体方法,如果多一个参数都可能找不到要拦截的方法。找到方法后然后去动态代理这个方法。

// 
public class Plugin implements InvocationHandler {
    private Object target;
    private Interceptor interceptor;
    private 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;
    }
    // 具体的代理实现
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取声明的方法列表
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        // 过滤需要拦截的方法
        if (methods != null && methods.contains(method)) {
            // 调用 Interceptor#intercept 插入自己的反射逻辑
            return interceptor.intercept(new Invocation(target, method, args));
        }
        return method.invoke(target, args);
    }
    /**
     * 用代理把自定义插件行为包裹到目标方法中,也就是 Plugin.invoke 的过滤调用
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        // 取得签名Map
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        // 取得要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)
        Class<?> type = target.getClass();
        // 取得接口
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        // 创建代理(StatementHandler)
        if (interfaces.length > 0) {
            // 代理
            return Proxy.newProxyInstance(
                    type.getClassLoader(),
                    interfaces,
                    new Plugin(target, interceptor, signatureMap));
        }
        return target;
    }
    /**
     * 获取方法签名组 Map
     */
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        // 取 Intercepts 注解
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        // 必须得有 Intercepts 注解,没有报错
        if (interceptsAnnotation == null) {
            throw new RuntimeException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        }
        // value是数组型,Signature的数组
        Signature[] sigs = interceptsAnnotation.value();
        // 每个 class 类有多个可能有多个 Method 需要被拦截
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
        for (Signature sig : sigs) {
            Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
            try {
                // 例如获取到方法;StatementHandler.prepare(Connection connection)、StatementHandler.parameterize(Statement statement)...
                Method method = sig.type().getMethod(sig.method(), sig.args());
                methods.add(method);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("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<Class<?>>();
        while (type != null) {
            for (Class<?> c : type.getInterfaces()) {
                // 拦截 ParameterHandler|ResultSetHandler|StatementHandler|Executor
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
            type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[interfaces.size()]);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

# 自定义

package cn.mybatis.test.plugin;

import cn.mybatis.executor.statement.StatementHandler;
import cn.mybatis.mapping.BoundSql;
import cn..mybatis.plugin.Interceptor;
import cn.mybatis.plugin.Intercepts;
import cn.mybatis.plugin.Invocation;
import cn.mybatis.plugin.Signature;

import java.sql.Connection;
import java.util.Properties;

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

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取StatementHandler
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 获取SQL信息
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        // 输出SQL
        System.out.println("拦截SQL:" + sql);
        // 放行
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {
        System.out.println("参数输出:" + properties.getProperty("test00"));
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
上次更新: 6/11/2025, 4:10:30 PM
核心功能拆解 工作流程
核心功能拆解 一二级缓存原理

← 核心功能拆解 工作流程 核心功能拆解 一二级缓存原理→

Theme by Vdoing | Copyright © 2023-2025
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式