概述

Plugin,意为插件,是mybatis为开发者提供的,对方法进行自定义编程的手段。其中用到了动态代理、反射方法,通过指定需要增强的对象与方法,进行程序编写。

核心类

主要涉及几个核心类:InterceptorPluginIntercepts

该增强功能的大致执行顺序为:

  1. 项目启动时,查询实现了Interceptor接口并且注册为Bean(或在xml文件中指定)的类,放入SqlSessionFactoryBean的Interceptor[]参数中,再由SqlSessionFactoryBean创建SqlSessionFactory的时候,将其放入Configuration参数中,留作后续调用

    // **注意interceptorsProvider,此为SpringBoot的configuration,会自动查询注册为Bean的Interceptor**
    public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
    ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
    ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
    ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
    ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.typeHandlers = typeHandlersProvider.getIfAvailable();
    this.languageDrivers = languageDriversProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
    this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();
    } @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    .....
    // **注意此处,将interceptors放入factory中**
    if (!ObjectUtils.isEmpty(this.interceptors)) {
    factory.setPlugins(this.interceptors);
    }
    .....
    } public class SqlSessionFactoryBean
    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    .......
    if (!isEmpty(this.typeAliases)) {
    Stream.of(this.typeAliases).forEach(typeAlias -> {
    targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
    LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
    });
    } if (!isEmpty(this.plugins)) {
    Stream.of(this.plugins).forEach(plugin -> {
    targetConfiguration.addInterceptor(plugin);
    LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
    });
    }
    ....... return this.sqlSessionFactoryBuilder.build(targetConfiguration);
    }
    }
  2. Configuration类在初始化ParameterHandlerResultSetHandlerStatementHandlerExecutor四个类时,会对它们进行一次封装,封装内容即为用Interceptors注册插件功能,达到增强效果

    public class Configuration {
    
    	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;
    } }
  3. Configuration执行的pluginAll方法,内部是通过遍历Interceptor数组的plugin方法实现的。该方法入参和出参都是Object类型,所以可以认为它能为所有类型对象都进行增强封装。Interceptor内部调用了Plugin的wrap方法,对Object对象进行了封装。

    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);
    } } 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
    } }
  4. Plugin方法实现了InvocationHandler动态代理类,并且wrap方法本身便是创建动态代理类。故Plugin类的职责有两项:

    1. 创建动态代理类,指定需要被代理(增强)的对象。此处为ExecutorHandler等。
    2. 指定被动态代理的对象,需要执行何种程序。重点关注invoke方法。
    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)) {
    return interceptor.intercept(new Invocation(target, method, args));
    }
    return method.invoke(target, args);
    } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
    }
    } // 省略getSignatureMap,getAllInterfaces方法
    ...
    }
  5. wrap方法执行时需要先通过interceptor获取signatureMap。SignatureIntercepts注解中的value值注解,由于此value的返回值是数组,所以Signature会多个存在,最后解析出的结果便为signatureMap。

    Signature注解的作用为标注被动态代理的对象,具体的类型(class),具体的方法,方法具体的参数。只有特定类型和方法才会执行Interceptor方法。

    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;
    } private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    **// 通过此代码可发现,实现Interceptor的类必须添加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 = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
    try {
    **// 通过Siganture的method与args,反射出Method对象,将其添加到map中
    // 作用是在执行动态代理invoke方法时,判断当前方法是否需要被interceptor执行**
    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;
    } }
  6. 通过阅读源码可知,开发者需要自己实现Interceptor,标记Intercepts注解,指定需要拦截的类、方法名,方法上的参数类型。并将Interceptor注册为Spring Bean。即可在interceptor方法中编写具体拦截代码。

实例

背景:在项目上为每一个需要插入至数据库中的实例对象,初始化id。

代码:

@Component
**// 拦截Executor类的update方法,该update方法会执行insert、update、delete操作**
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class MybatisUpdateInterceptor implements Interceptor { // 雪花算法id生成器
@Autowired
private IdGenerator idGenerator; @Override
public Object intercept(Invocation invocation) throws Throwable {
Method method = invocation.getMethod();
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
**// 判断是否为insert方法**
if (ms.getSqlCommandType() != SqlCommandType.INSERT) {
return invocation.proceed();
}
BaseEntity entity = (BaseEntity) invocation.getArgs()[1];
if (entity.getId() == null) {
entity.setId(idGenerator.generate());
}
return method.invoke(invocation.getTarget(), invocation.getArgs());
} }

总结

项目开发者可灵活运用plugin,为数据库操作进行增强。日常开发中也可借鉴此流程,通过动态代理方式设计拦截/增强手段。

mybatis plugin源码解析的更多相关文章

  1. MyBatis详细源码解析(上篇)

    前言 我会一步一步带你剖析MyBatis这个经典的半ORM框架的源码! 我是使用Spring Boot + MyBatis的方式进行测试,但并未进行整合,还是使用最原始的方式. 项目结构 导入依赖: ...

  2. MyBatis 3源码解析(一)

    一.SqlSessionFactory 对象初始化 //加载全局配置文件 String resource = "mybatis-config.xml"; InputStream i ...

  3. Mybatis SqlNode源码解析

    1.ForEachSqlNode mybatis的foreach标签可以将列表.数组中的元素拼接起来,中间可以指定分隔符separator <select id="getByUserI ...

  4. Mybatis SqlSessionTemplate 源码解析

    As you may already know, to use MyBatis with Spring you need at least an SqlSessionFactory and at le ...

  5. MyBatis 3源码解析(四)

    四.MyBatis 查询实现 Employee empById = mapper.getEmpById(1); 首先会调用MapperProxy的invoke方法 @Override public O ...

  6. MyBatis 3源码解析(三)

    三.getMapper获取接口的代理对象 1.先调用DefaultSqlSession的getMapper方法.代码如下: @Override public <T> T getMapper ...

  7. MyBatis 3源码解析(二)

    二.获取SqlSession对象 1.首先调用DefaultSqlSessionFactory 的 openSession 方法,代码如下: @Override public SqlSession o ...

  8. mybatis源码解析1--前言

    在开始分析mybatis源码之前,需要定一个目标,也就是我们不是为了读源码而去读,一定是带着问题去读,在读的时候去寻找到答案,然后再读码的同时整理总结,学习一些高级的编码方式和技巧. 首先我们知道my ...

  9. Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码

    在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能 ...

  10. Mybatis 系列6-结合源码解析节点配置:objectFactory、databaseIdProvider、plugins、mappers

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

随机推荐

  1. 【Azure 应用服务】App Server 部署后,Docker报错,找不到8080端口

    问题描述 App Service for Container.  Docker Image 推送到ACR(向 Azure 容器注册表), 配置App Service并部署成功了.查看Docker日志( ...

  2. 2、zookeeper的简单命令

    Zookeeper的常用命令本篇不包括权限acl相关以及集群相关,那些要另开篇章.使用的版本是Zookeeper3.4.14,不同版本会有一定的差异性. 节点的存储信息 新增命令 语法:create ...

  3. Lambda表达式(匿名函数)

    C++11中引入了lambda表达式,定义匿名的内联函数. 我们可以直接原地定义函数而不用再跑到外面去定义函数跳来跳去. 同时在stl的排序上也有作用. [capture] (parameters) ...

  4. Openssl命令详解 - 证书篇

    生成自签证书 # 设置CA证书subject CA_SUBJ="/C=CN/ST=ShanDong/L=JiNan/O=sec/OU=sec/CN=www.hxy.com/emailAddr ...

  5. 自我总结的git的使用

    git是什么 git是一个分布式版本控制工具,github是代码托管平台. git有什么用 保存文件的所有修改记录 使用版本号进行区分 随时可浏览历史版本记录 可还原到历史指定版本 对比不同版本的文件 ...

  6. -Dmaven.multiModuleProjectDirectory system propery is not set解决方案

    myeclipse中使用maven插件的时候,运行run as maven build的时候报错 -Dmaven.multiModuleProjectDirectory system propery ...

  7. Android Studio导入Android 4.2.2的WiFi-Display系统源码

    Sink源码概述 Miracast Sink端源码最早出现在Android 4.2.2上,通过googlesource可以很方便的查看: https://android.googlesource.co ...

  8. 多线程系列(二十一) -ForkJoin使用详解

    一.摘要 从 JDK 1.7 开始,引入了一种新的 Fork/Join 线程池框架,它可以把一个大任务拆成多个小任务并行执行,最后汇总执行结果. 比如当前要计算一个数组的和,最简单的办法就是用一个循环 ...

  9. 爬虫实战:从HTTP请求获取数据解析社区

    在过去的实践中,我们通常通过爬取HTML网页来解析并提取所需数据,然而这只是一种方法.另一种更为直接的方式是通过发送HTTP请求来获取数据.考虑到大多数常见服务商的数据都是通过HTTP接口封装的,因此 ...

  10. C++ Qt开发:QUdpSocket实现组播通信

    Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QUd ...