序
本文次要钻研一下 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 的代理对象,从而实现对这些对象的拦挡成果