mybatis的Interceptor機(jī)制
序
本文主要研究一下mybatis的Interceptor機(jī)制
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方法,其參數(shù)為Invocation類型,同時(shí)默認(rèn)提供了plugin方法,通過Plugin.wrap(target, this)進(jìn)行包裝
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方法則是反射執(zhí)行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實(shí)現(xiàn)了java.lang.reflect.InvocationHandler方法,其invoke方法主要是多了一層判斷,判斷interceptor的signatureMap有沒有包含對(duì)應(yīng)的方法,有則執(zhí)行interceptor.intercept,同時(shí)包裝了Invocation參數(shù)傳遞過去
而Plugin的wrap方法則是判斷interceptor有沒有攔截target對(duì)應(yīng)的接口,如果有則通過Proxy.newProxyInstance返回代理對(duì)象方便后續(xù)進(jìn)行攔截
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方法對(duì)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的時(shí)候,會(huì)獲取定義的interceptor,實(shí)例化之后通過configuration.addInterceptor添加進(jìn)去
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不為空時(shí),通過targetConfiguration.addInterceptor(plugin)將interceptor注冊(cè)進(jìn)去
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不為空時(shí),通過SqlSessionFactory的setPlugins方法把interceptors添加進(jìn)去;MybatisAutoConfiguration標(biāo)注了@Configuration注解,該注解標(biāo)注了@Component,因而這些interceptors則是通過構(gòu)造器從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方法,這些方法會(huì)對(duì)ParameterHandler、ResultSetHandler、StatementHandler、Executor執(zhí)行interceptorChain.pluginAll方法,則創(chuàng)建作用了所有interceptor的代理對(duì)象,從而實(shí)現(xiàn)對(duì)這些對(duì)象的攔截效果
小結(jié)
- mybatis的Interceptor機(jī)制使用的是jdk的Proxy.newProxyInstance的方式
- 在掃描xml的時(shí)候把interceptor注冊(cè)到configuration中,針對(duì)spring的場(chǎng)景,在MybatisAutoConfiguration中注入所有托管的interceptor,之后在構(gòu)造SqlSessionFactory的時(shí)候把interceptor注冊(cè)到configuration中
- 最后Configuration提供了newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor方法,創(chuàng)建作用了所有interceptor的代理對(duì)象,從而實(shí)現(xiàn)對(duì)這些對(duì)象的攔截效果
以上就是mybatis的Interceptor機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于mybatis的Interceptor機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Mybatis-Plus中分頁(yè)插件PaginationInterceptor的使用
- Mybatis-plus數(shù)據(jù)權(quán)限D(zhuǎn)ataPermissionInterceptor實(shí)現(xiàn)
- 使用mybatis的interceptor修改執(zhí)行sql以及傳入?yún)?shù)方式
- 基于MybatisPlus插件TenantLineInnerInterceptor實(shí)現(xiàn)多租戶功能
- MyBatisPlus PaginationInterceptor分頁(yè)插件的使用詳解
- 解決mybatis-plus3.4.1分頁(yè)插件PaginationInterceptor和防止全表更新與刪除插件SqlExplainInterceptor過時(shí)失效問題
相關(guān)文章
Java中關(guān)于char類型變量能夠輸出中文的問題
這篇文章主要介紹了Java中關(guān)于char類型變量能夠輸出中文的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
Spring入門配置和DL依賴注入實(shí)現(xiàn)圖解
這篇文章主要介紹了Spring入門配置和DL依賴注入實(shí)現(xiàn)圖解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
下載遠(yuǎn)程maven倉(cāng)庫(kù)的jar?手動(dòng)放到本地倉(cāng)庫(kù)詳細(xì)操作
這篇文章主要介紹了如何下載遠(yuǎn)程maven倉(cāng)庫(kù)的jar?手動(dòng)放到本地倉(cāng)庫(kù),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
Java定時(shí)/延時(shí)任務(wù)之Timer用法詳解
在?Java?Development?Kit?(JDK)?中,java.util.Timer?是一個(gè)用于調(diào)度任務(wù)的工具類,本文主要來和大家聊聊Timer的用法,有需要的小伙伴可以了解下2024-12-12
SpringBoot之@ConditionalOnProperty注解使用方法
在平時(shí)業(yè)務(wù)中,我們需要在配置文件中配置某個(gè)屬性來決定是否需要將某些類進(jìn)行注入,讓Spring進(jìn)行管理,而@ConditionalOnProperty能夠?qū)崿F(xiàn)該功能,文中有詳細(xì)的代碼示例,需要的朋友可以參考下2023-05-05
基于SpringBoot + MyBatis-Plus高效實(shí)現(xiàn)數(shù)據(jù)變更記錄
在應(yīng)用開發(fā)過程中,在某些情況下,需要實(shí)現(xiàn)數(shù)據(jù)的變更記錄,以便于在未來進(jìn)行數(shù)據(jù)操作變更的回溯,那有什么辦法高效實(shí)現(xiàn)數(shù)據(jù)操作變更記錄,而不需要一個(gè)一個(gè)地方去加呢?答案當(dāng)然是有,今天小編就給大家介紹下如何基于SpringBoot + MyBatis-Plus高效實(shí)現(xiàn)數(shù)據(jù)的變更記錄,2026-02-02
關(guān)于Jedis的用法以及Jedis使用Redis事務(wù)
這篇文章主要介紹了關(guān)于Jedis的用法以及Jedis使用Redis事務(wù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
idea設(shè)置JVM運(yùn)行參數(shù)的幾種方式
對(duì)JVM運(yùn)行參數(shù)進(jìn)行修改是JVM性能調(diào)優(yōu)的重要手段,本文主要介紹了idea設(shè)置JVM運(yùn)行參數(shù)的幾種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04

