• mybatis plugin源码解析


    概述

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

    核心类

    主要涉及几个核心类:InterceptorPluginIntercepts

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

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

      // **注意interceptorsProvider,此为SpringBoot的configuration,会自动查询注册为Bean的Interceptor**
      public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider interceptorsProvider,
            ObjectProvider typeHandlersProvider, ObjectProvider languageDriversProvider,
            ResourceLoader resourceLoader, ObjectProvider databaseIdProvider,
            ObjectProvider> configurationCustomizersProvider,
            ObjectProvider> 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 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 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, Set> signatureMap;
      
        private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {
          this.target = target;
          this.interceptor = interceptor;
          this.signatureMap = signatureMap;
        }
      
      	// **创建动态代理对象**
        public static Object wrap(Object target, Interceptor interceptor) {
          Map, Set> 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 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, Set> signatureMap;
      
        private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {
          this.target = target;
          this.interceptor = interceptor;
          this.signatureMap = signatureMap;
        }
      
        private static Map, Set> 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, Set> signatureMap = new HashMap<>();
          for (Signature sig : sigs) {
            Set 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,为数据库操作进行增强。日常开发中也可借鉴此流程,通过动态代理方式设计拦截/增强手段。

  • 相关阅读:
    华为手机备忘录的妙用——8个小技巧总结
    iperf简介与下载安装
    Mybatis动态sql全面详解
    Glide加载图片占位图问题,CustomViewTarget加载图片占位图问题
    读excel报错LeftoverDataException
    从0备战蓝桥杯:找出只出现一次的数字,数单身狗
    如何合并多个PDF文件?这几个小妙招快来码住吧
    阿里巴巴中国站按图搜索1688商品(拍立淘) API 返回值说明
    【无标题】Gradle学习
    rocket-chip verilator@ubuntu20.04验证环境操作指南
  • 原文地址:https://www.cnblogs.com/sumray/p/18126151