Mybatis拦截器源码分析及拦截器应用

一 、介绍

拦截器简单来说就是可以拦截某些方法,在该方法的执行前后加上某些逻辑来实现我们的需求

二、使用介绍

1.先自定义一个拦截器,实现Interceptor接口,加上注解@Intercepts({@Signature})

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

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
       System.out.println("拦截器测试....");
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

2.添加拦截器到Mybatis的Configuration中(介绍两种实现方式):
方式一:

@Configuration
public class MybatisConfig {
    @Bean
    ConfigurationCustomizer mybatisConfigurationCustomizer() {
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                // 在configuration中添加自定义的拦截器
                configuration.addInterceptor(new TableShardInterceptor());
            }
        };
    }
}

方式二:
使用xml配置


    

这样就可以实现一个简单的拦截器了

三、源码分析

源码分析才是本文的重点
从MybatisAutoConfiguration中可以看到在初始化时拿到ConfigurationCustomizer实例

public class MybatisAutoConfiguration implements InitializingBean {
    ...

  private final List configurationCustomizers;

	// 通过构造器注入的方式获取到configurationCustomizers
	// 在定义拦截器后我们需要显现一个configurationCustomizer实例,在customize方法中添加定义好的拦截器
  public MybatisAutoConfiguration(MybatisProperties properties,
      ObjectProvider interceptorsProvider,
      ResourceLoader resourceLoader,
      ObjectProvider databaseIdProvider,
      ObjectProvider> configurationCustomizersProvider) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
  }

ConfigurationCustomizer.customize()方法的调用:
实例化SqlSessionFactory 时调用

  @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());
    }
   ...
  }

  private void applyConfiguration(SqlSessionFactoryBean factory) {
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
      configuration = new Configuration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
      for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
      	// 调用添加拦截器到configuration
        customizer.customize(configuration);
      }
    }
    factory.setConfiguration(configuration);
  }

拦截器的生效:
调用拦截器链的pluginAll(Object target)方法

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

interceptor.plugin(target)这里用到了jdk的动态代理模式,mybatis中提供了Plugin 工具类,调用wrap()方法实现对目标对象的代理


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

// 获取到需要拦截的类和方法信息
  private static Map, Set> getSignatureMap(Interceptor interceptor) {
  // 获取@Intercepts注解
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    
    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, Set>();
    for (Signature sig : sigs) {
      Set methods = signatureMap.get(sig.type());
      if (methods == null) {
        methods = new HashSet();
        signatureMap.put(sig.type(), methods);
      }
      try {
        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;
  }

  private static Class[] getAllInterfaces(Class type, Map, Set> signatureMap) {
    Set> interfaces = new HashSet>();
    while (type != null) {
      for (Class c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class[interfaces.size()]);
  }
}

Mybatis拦截器的适用范围:
Executor,ParameterHandler,ResultSetHandler,StatementHandler的部分方法
(看哪些方法中调用到了interceptorChain.pluginAll())

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // interceptorChain.pluginAll
    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);
    // interceptorChain.pluginAll
    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);
    // interceptorChain.pluginAll
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

  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);
    }
    // interceptorChain.pluginAll
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
    }

后续会添加一个利用拦截器来实现分表路由操作的小demo

你可能感兴趣的:(mybatis)