mybatis的Interceptor機制
序
本文主要研究一下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方法,其參數(shù)為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方法則是反射執(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實現(xiàn)了java.lang.reflect.InvocationHandler方法,其invoke方法主要是多了一層判斷,判斷interceptor的signatureMap有沒有包含對應的方法,有則執(zhí)行interceptor.intercept,同時包裝了Invocation參數(shù)傳遞過去
而Plugin的wrap方法則是判斷interceptor有沒有攔截target對應的接口,如果有則通過Proxy.newProxyInstance返回代理對象方便后續(xù)進行攔截
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執(zhí)行interceptorChain.pluginAll方法,則創(chuàng)建作用了所有interceptor的代理對象,從而實現(xiàn)對這些對象的攔截效果
小結
- mybatis的Interceptor機制使用的是jdk的Proxy.newProxyInstance的方式
- 在掃描xml的時候把interceptor注冊到configuration中,針對spring的場景,在MybatisAutoConfiguration中注入所有托管的interceptor,之后在構造SqlSessionFactory的時候把interceptor注冊到configuration中
- 最后Configuration提供了newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor方法,創(chuàng)建作用了所有interceptor的代理對象,從而實現(xiàn)對這些對象的攔截效果
以上就是mybatis的Interceptor機制的詳細內(nèi)容,更多關于mybatis的Interceptor機制的資料請關注腳本之家其它相關文章!
- Mybatis-Plus中分頁插件PaginationInterceptor的使用
- Mybatis-plus數(shù)據(jù)權限DataPermissionInterceptor實現(xiàn)
- 使用mybatis的interceptor修改執(zhí)行sql以及傳入?yún)?shù)方式
- 基于MybatisPlus插件TenantLineInnerInterceptor實現(xiàn)多租戶功能
- MyBatisPlus PaginationInterceptor分頁插件的使用詳解
- 解決mybatis-plus3.4.1分頁插件PaginationInterceptor和防止全表更新與刪除插件SqlExplainInterceptor過時失效問題
相關文章
Simple Java Mail郵件發(fā)送實現(xiàn)過程解析
這篇文章主要介紹了Simple Java Mail郵件發(fā)送實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-11-11Springboot JPA打印SQL語句及參數(shù)的實現(xiàn)
在SpringBoot項目中調(diào)試和優(yōu)化數(shù)據(jù)庫操作是很常見的需求,本文主要介紹了Springboot JPA打印SQL語句及參數(shù)的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-06-06springboot在idea下debug調(diào)試熱部署問題
這篇文章主要介紹了springboot在idea下debug調(diào)試熱部署問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02SpringBoot應用中出現(xiàn)的Full GC問題的場景與解決
這篇文章主要為大家詳細介紹了SpringBoot應用中出現(xiàn)的Full GC問題的場景與解決方法,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2025-04-04Spring?Cloud?Feign?使用對象參數(shù)的操作
這篇文章主要介紹了Spring?Cloud?Feign?如何使用對象參數(shù)的問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-02-02