美文网首页
聊聊mybatis的Interceptor机制

聊聊mybatis的Interceptor机制

作者: go4it | 来源:发表于2023-08-29 09:47 被阅读0次

    本文主要研究一下mybatis的Interceptor机制

    Interceptor

    org/apache/ibatis/plugin/Interceptor.java

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

    Interceptor定义了intercept方法,其参数为Invocation类型,同时默认提供了plugin方法,通过Plugin.wrap(target, this)进行包装

    Invocation

    org/apache/ibatis/plugin/Invocation.java

    public class Invocation {
    
      private final Object target;
      private final Method method;
      private final Object[] args;
    
      public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
      }
    
      public Object getTarget() {
        return target;
      }
    
      public Method getMethod() {
        return method;
      }
    
      public Object[] getArgs() {
        return args;
      }
    
      public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
      }
    
    }
    

    Invocation定义了target、method、args属性,提供了proceed方法则是反射执行method方法

    Plugin

    org/apache/ibatis/plugin/Plugin.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);
        }
      }
    
      //......
    }  
    

    Plugin实现了java.lang.reflect.InvocationHandler方法,其invoke方法主要是多了一层判断,判断interceptor的signatureMap有没有包含对应的方法,有则执行interceptor.intercept,同时包装了Invocation参数传递过去
    而Plugin的wrap方法则是判断interceptor有没有拦截target对应的接口,如果有则通过Proxy.newProxyInstance返回代理对象方便后续进行拦截

    InterceptorChain

    org/apache/ibatis/plugin/InterceptorChain.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);
      }
    
    }
    

    InterceptorChain定义了interceptors,它提供了pluginAll方法对target代理所有的interceptor

    Configuration

    org/apache/ibatis/session/Configuration.java

      protected final InterceptorChain interceptorChain = new InterceptorChain();
    
      public void addInterceptor(Interceptor interceptor) {
        interceptorChain.addInterceptor(interceptor);
      }
    
    

    Configuration定义了interceptorChain,它通过addInterceptor方法往interceptorChain添加interceptor

    XMLConfigBuilder

    org/apache/ibatis/builder/xml/XMLConfigBuilder.java

      private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor()
                .newInstance();
            interceptorInstance.setProperties(properties);
            configuration.addInterceptor(interceptorInstance);
          }
        }
      }
    

    XMLConfigBuilder在解析xml的plugin的时候,会获取定义的interceptor,实例化之后通过configuration.addInterceptor添加进去

    SqlSessionFactoryBean

    org/mybatis/spring/SqlSessionFactoryBean.java

      private Interceptor[] plugins;
    
      public void addPlugins(Interceptor... plugins) {
        setPlugins(appendArrays(this.plugins, plugins, Interceptor[]::new));
      }
    
      public void setPlugins(Interceptor... plugins) {
        this.plugins = plugins;
      }
    
      protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    
        final Configuration targetConfiguration;
    
        XMLConfigBuilder xmlConfigBuilder = null;
        if (this.configuration != null) {
          targetConfiguration = this.configuration;
          if (targetConfiguration.getVariables() == null) {
            targetConfiguration.setVariables(this.configurationProperties);
          } else if (this.configurationProperties != null) {
            targetConfiguration.getVariables().putAll(this.configurationProperties);
          }
        } else if (this.configLocation != null) {
          xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
          targetConfiguration = xmlConfigBuilder.getConfiguration();
        } else {
          LOGGER.debug(
              () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
          targetConfiguration = new Configuration();
          Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
        }
    
        Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
        Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
        Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
    
        //......
    
        if (!isEmpty(this.plugins)) {
          Stream.of(this.plugins).forEach(plugin -> {
            targetConfiguration.addInterceptor(plugin);
            LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
          });
        }
    
        if (hasLength(this.typeHandlersPackage)) {
          scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
              .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
              .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
        }
    
        if (!isEmpty(this.typeHandlers)) {
          Stream.of(this.typeHandlers).forEach(typeHandler -> {
            targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
            LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
          });
        }
    
        targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
    
        //......
    
        return this.sqlSessionFactoryBuilder.build(targetConfiguration);
      }
    

    SqlSessionFactoryBean的buildSqlSessionFactory方法在判断plugins不为空时,通过targetConfiguration.addInterceptor(plugin)将interceptor注册进去

    MybatisAutoConfiguration

    org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.java

    @org.springframework.context.annotation.Configuration
    @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
    @ConditionalOnSingleCandidate(DataSource.class)
    @EnableConfigurationProperties(MybatisProperties.class)
    @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
    public class MybatisAutoConfiguration implements InitializingBean {
    
      private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
    
      private final MybatisProperties properties;
    
      private final Interceptor[] interceptors;
    
      //......
    
      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);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
          factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }
        applyConfiguration(factory);
        if (this.properties.getConfigurationProperties() != null) {
          factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }
        if (!ObjectUtils.isEmpty(this.interceptors)) {
          factory.setPlugins(this.interceptors);
        }
    
        //......
      }  
    

    MybatisAutoConfiguration的sqlSessionFactory方法,在判断interceptors不为空时,通过SqlSessionFactory的setPlugins方法把interceptors添加进去;MybatisAutoConfiguration标注了@Configuration注解,该注解标注了@Component,因而这些interceptors则是通过构造器从spring中注入的

    Configuration.pluginAll

    org/apache/ibatis/session/Configuration.java

      public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
          BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,
            parameterObject, boundSql);
        return (ParameterHandler) interceptorChain.pluginAll(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);
        return (ResultSetHandler) interceptorChain.pluginAll(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);
        return (StatementHandler) interceptorChain.pluginAll(statementHandler);
      }
    
      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : 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);
        }
        return (Executor) interceptorChain.pluginAll(executor);
      }
    

    Configuration提供了newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor方法,这些方法会对ParameterHandler、ResultSetHandler、StatementHandler、Executor执行interceptorChain.pluginAll方法,则创建作用了所有interceptor的代理对象,从而实现对这些对象的拦截效果

    小结

    • mybatis的Interceptor机制使用的是jdk的Proxy.newProxyInstance的方式
    • 在扫描xml的时候把interceptor注册到configuration中,针对spring的场景,在MybatisAutoConfiguration中注入所有托管的interceptor,之后在构造SqlSessionFactory的时候把interceptor注册到configuration中
    • 最后Configuration提供了newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor方法,创建作用了所有interceptor的代理对象,从而实现对这些对象的拦截效果

    相关文章

      网友评论

          本文标题:聊聊mybatis的Interceptor机制

          本文链接:https://www.haomeiwen.com/subject/wcpnmdtx.html