mybatis plugin源码解析

概述

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

核心类

主要涉及几个核心类:InterceptorPluginIntercepts

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

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

    java 复制代码
    // **注意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注册插件功能,达到增强效果

    java 复制代码
    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对象进行了封装。

    java 复制代码
    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方法。
    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)) {
            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方法。

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

代码:

java 复制代码
@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,为数据库操作进行增强。日常开发中也可借鉴此流程,通过动态代理方式设计拦截/增强手段。

相关推荐
间彧1 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧1 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧1 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧1 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧1 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧1 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧1 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang2 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构
草明2 小时前
Go 的 IO 多路复用
开发语言·后端·golang
蓝-萧2 小时前
Plugin ‘mysql_native_password‘ is not loaded`
java·后端