mybatis实现自定义插件

Posted 我爱看明朝

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis实现自定义插件相关的知识,希望对你有一定的参考价值。

mybatis实现自定义插件

写本文的原因是在写制品库代码时遇到了下面了两个问题:

  1. Signature注解的各个属性的可选值
  2. 为什么会@component和@bean注释的拦截器的不生效

遂阅读了mybatis部分源码,找出问题2症结所在

@Signature(
			type = StatementHandler.class,
			method = "prepare",
			args = {Connection.class, Integer.class})

type:
ParameterHanlder
ResultSetHanler
StatementHanlder
Executor

method:
四个被拦截接口的所有方法
ParameterHanlder: getParameterObject, setParameters
ResultSetHanler: handlerResultSets,handlerOutputParameters
StatementHanlder: prepare,parameterize,batch,update,query
Executor: update,query,flushStatements,commit,rollback,getTransaction,close,isClosed

args:
被拦截方法参数的类

自定义插件


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

    private final Logger logger = LoggerFactory.getLogger(SQLStatsInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //自定义插件主要逻辑代码

        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {
        logger.info(properties.toString());
    }
}

加载自定义插件的三种方式

1. 手动使用sqlSessionFactory加载插件

  public String myInterceptor(SqlSessionFactory sqlSessionFactory) {

        sqlSessionFactory.getConfiguration().addInterceptor(new MyInterceptor());
        return "interceptor";
    }

2. 使用@bean的方式加载插件

    @Bean
    public MyInterceptor generalPlugin() {
        return new MyInterceptor();
    }
    

3. @Component直接注解在自定义插件加载

@Intercepts({
	@Signature(
			.................
})
@Component
class  MyInterceptor{

}


上面23中方式会在MybatisAutoConfiguration的构造器中获取到自定义插件,当用户没有自己
创建SqlSessionFactory时会执行sqlSessionFactory()方法,这个时候会加载自定义的插件。

```java
//执行完上面的会进入: MybatisAutoConfiguration的构造器加载插件
  public MybatisAutoConfiguration() {
       this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
   }

 @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }
    }

pageHelper的自定配置是在MybatisAutoConfiguration之后的。

@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
    public MybatisAutoConfiguration() {
      this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
  }

}

@AutoConfigureAfter({MybatisAutoConfiguration.class})
public class PageHelperAutoConfiguration {

}

网上有文章说@Component和@Bean不生效的原因是pageHelper插件导致的。但是经过实测:发现是没有关系。

pageHelper的自定配置是在MybatisAutoConfiguration之后,我们打断点在MybatisAutoConfiguration,发现即使有
pageHeper但是@Component和@Bean注解的bean是被MybatisAutoConfiguration存储在interceptors属性可,
但是并没有被执行factory.setPlugins(this.interceptors);方法加载到sqlSessionFactory。因此不存在pageHelper影响了
自定义插件的加载。

当有开发者自己创建了sqlSessionFactory那么,自定义插件一定要通过上述方式1手动加载到sqlSessionFactory中.

源码

加载插件


sqlSessionFactory.getConfiguration().addInterceptor(new YourInterceptor());

 public void addInterceptor(Interceptor interceptor) {
   //添加插件到责任链中
    interceptorChain.addInterceptor(interceptor);
  }


public class InterceptorChain {

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

// 四大金刚加入到插件通过责任链设计模式进行链式处理
// parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
// resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
// statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
// executor = (Executor) interceptorChain.pluginAll(executor);
  public Object pluginAll(Object target) {
    // 责任链模式, 多个插件按照加入在list中的顺序进行处理
    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);
  }

}

public class Configuration{
  public ParameterHandler newParameterHandler(){
     parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  }

  public ResultSetHandler newResultSetHandler(){
      resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  }

  public StatementHandler newStatementHandler(){
      statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  }

  public Executor newExecutor(){
      executor = (Executor) interceptorChain.pluginAll(executor);
  }
}

时序
new出四大金刚 --> interceptorChain.pluginAll --> target = interceptor.plugin(target); —> Plugin.wrap(target, this); Plugin:动态代理 --> Plugin.invoke --> interceptor.intercept

new出四大金刚
interceptorChain.pluginAll
interceptor.plugin
Plugin.wrap
Plugin.invoke
interceptor.intercept

public class Congiguration{
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  //省略部分代码
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
}


public class InterceptorChain {

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

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      //加载所有插件
      target = interceptor.plugin(target);
    }
    return target;
  }
}

// Plugin实现jdk的InvocationHandler接口表示这个是动态代理
public class Plugin implements InvocationHandler {

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

//最终通过invoke方法运行具体的代理逻辑
// method ==  interceptor(就是用户自己实现的自定义插件)
  @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);
    }
  }
}

扩展知识

@ConditionalOnClass

条件创建bean当,当SqlSessionFactory,SqlSessionFactoryBean这个两个类存在是,才会创建被注解的bean
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})

@ConditionalOnBean

条件创建bean,当DataSource的bean存在,才可以创建被这个被注解的bean
@ConditionalOnBean({DataSource.class})

@ConditionalOnMissingBean

当指定的bean不存在,再执行代码,否则不执行。
下面代码的意思是:当spring的容器中没有SqlSessionFactory时才执行下面方法的代码
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory() {

}

总结

1.自定义插件可拦截处理的对象:mybatis四大金刚 ParameterHanlder、 ResultSetHanler、 StatementHanlder、 Executor
2.插件使用责任链模式:四大金刚加入到插件通过责任链设计模式进行链式处理
3.有三种方式可以把自定义插件加载到mybatis中
4.@component和@bean注释的拦截器不生效的原因是用户new sqlSessionFactory
5.@Signature注解的三个参数type、method、args分别对应的值是:四大金刚、要拦截type指定类的方法、指定methos方法的参数类
6.插件机制用到用jdk的动态代理:Plugin实现jdk的InvocationHandler接口表示这个是动态代理
7.扩展知识spring中的注解:@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnMissingBean

https://mybatis.org/mybatis-3/configuration.html#plugins
http://www.mybatis.cn/726.html
http://www.mybatis.cn/archives/685.html

MyBatis四大核心对象之ParameterHandler

以上是关于mybatis实现自定义插件的主要内容,如果未能解决你的问题,请参考以下文章

#私藏项目实操分享# Mybatis自定义拦截器与插件开发

VSCode插件开发全攻略代码片段设置自定义欢迎页

MyBatis自定义插件机制分析(源码级剖析)

MyBatis 插件使用-自定义简单的分页插件

MyBatis自定义插件实现按日期分表功能

Mybatis框架---Mybatis自定义插件生成雪花ID做为表主键项目