Mybatis链路分析:JDK动态代理和责任链模式的应用

2024-08-30 10:28

本文主要是介绍Mybatis链路分析:JDK动态代理和责任链模式的应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

c1e38ab917451ae60c6705914a983e39.jpeg

背景

此前写过关于代理模式的文章,参考:代理模式

动态代理功能:生成一个Proxy代理类,Proxy代理类实现了业务接口,而通过调用Proxy代理类实现的业务接口,实际上会触发代理类的invoke增强处理方法。

责任链功能:可以动态地组合处理者,增加或删除处理者,而不需要修改客户端代码;可以灵活地处理请求,每个处理者可以选择处理请求或将请求传递给下一个处理者。

MybatisAutoConfiguration

这是最初的Mybatis的自动加载类。

org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration@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 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) {this.properties = properties;// 【1】this.interceptors = interceptorsProvider.getIfAvailable();......}@Bean@ConditionalOnMissingBeanpublic 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());}// 【2】if (!ObjectUtils.isEmpty(this.interceptors)) {factory.setPlugins(this.interceptors);}......return factory.getObject();}}

代码分析:

  • 【1】interceptorsProvider.getIfAvailable();获取Interceptor接口的所有的bean,并加载到内存interceptors里

  • 【2】构造SqlSessionFactoryBean,会用到内存的interceptors,填充拦截器bean到SqlSessionFactoryBean里面。

SqlSessionFactoryBean#afterPropertiesSet:初始化完成后执行

因为SqlSessionFactoryBean实现了InitializingBean接口,必然有一个afterPropertiesSet()的实现方法:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {@Overridepublic void afterPropertiesSet() throws Exception {notNull(dataSource, "Property 'dataSource' is required");notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),"Property 'configuration' and 'configLocation' can not specified with together");// 【3】this.sqlSessionFactory = buildSqlSessionFactory();}
}

代码分析:

  • 【3】这里会构造一个sqlSessionFactory,并调用了buildSqlSessionFactory()

SqlSessionFactoryBean#buildSqlSessionFactory:构造SqlSessionFactory

里面通过遍历SqlSessionFactoryBean的interceptors,逐个把拦截器加载到SqlSessionFactory的interceptorChain里面。

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {......// 【4】if (!isEmpty(this.plugins)) {Stream.of(this.plugins).forEach(plugin -> {targetConfiguration.addInterceptor(plugin);LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");});}// 【5】return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

代码分析:

  • 【4】逐个把拦截器加载到targetConfiguration对象的interceptorChain里面(也就是拦截器责任链了)。

  • 【5】最终通过sqlSessionFactoryBuilder建造者模式,完成一个对象创建:new DefaultSqlSessionFactory(config)

DefaultSqlSessionFactory#构造器

public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}
}

到此,完成Mybatis的插件bean的类加载和插件责任链的初始化。

上述的实现逻辑,,拆分到了只包含部分逻辑的、功能单一的Handler处理类里,开发人员可以按照业务需求将多个Handler对象组合成一条责任链,实现请求的处理。

那么Mybatis插件什么时候发挥作用呢?

自然是每个sqlSession创建时,在返回Executor对象前,会对执行器进行一个pluginAll的插件处理。

我们发起一次请求,最终会映射打开一个session会话:http://localhost:8891/user_select?id=2

最终debug到下面源码。

DefaultSqlSessionFactory#openSessionFromDataSource

最终debug到:org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource

public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {// 【1】final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);// 【2】tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// 【3】final Executor executor = configuration.newExecutor(tx, execType);// 【4】return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
}

源码分析:

  1. 【1】获取环境变量

  2. 【2】创建新事务

  3. 【3】创建一个新的Executor执行器(非常关键)

  4. 【4】返回新构造的DefaultSqlSession

configuration#newExecutor:非常关键

public class Configuration {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);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;}
}

interceptorChain.pluginAll(executor)

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;}}
  • 最终返回的是一个动态代理了Plugin类的自动生产对象。

Interceptor#plugin

public interface Interceptor {Object intercept(Invocation invocation) throws Throwable;default Object plugin(Object target) {// 【1】return Plugin.wrap(target, this);}default void setProperties(Properties properties) {// NOP}
}
  • 此处的 Plugin.wrap(target, this) 是一个静态方法,本质是对插件进行

    动态代理,最终返回的是一个动态代理了Plugin类的自动生产对象。

org.apache.ibatis.plugin.Plugin#wrap

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

源码分析:

8002fed400aa97bca32e90c74078d413.png

  • 最核心的部分,就是Proxy.newProxyInstance

    • target:Executor执行器

    • Interceptor:拦截器

    • signatureMap:拦截签名

    • new 了一个Plugin类

    • 构造对Plugin类的动态代理,会自动生成一个代理类A#Plugin,最终执行的还是Plugin类的invoke方法

  • 于是wrap最终返回的是一个动态代理了Plugin类的自动生产对象。

  • 代理器是Plugin,被代理类是target(Executor或Handler),

至此,我们分析了完成代理模式的应用部分,下面是责任链模式的应用。

执行SQL时机:SqlSessionInterceptor

也是一个动态代理,

org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke

4c887eed1463cd6242d169ac1bf6e63b.png

最终执行的逻辑,是调用sqlSession去接args参数,这个反射执行的方法是:

SqlSession.selectOne(java.lang.String,java.lang.Object)

而selectOne最终调用了

DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}

源码分析:

  • executor.query:是最终的Executor执行器query方法逻辑。还记得上面一个步骤吗?自动生成一个代理类A#Plugin,它实现了对Executor的增强处理,这块增强处理逻辑要回到Plugin#invoke方法:

@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);}
}
  • 这里会根据signatureMap的调用点,来判断是否执行interceptor的拦截逻辑。

  • 而这里的拦截逻辑,就是我们实现好的,Interceptor拦截器类的intercept方法啦。

    • new好的Invocation,实际就是调用点。

  • 通过InterceptorChain拦截器链,对Executor进行增强

总结

608af4af16f0937a52e68cc481095185.png

从图中可以知道,Mybatis的拦截器链运用了动态代理和责任链模式:

  • 其实就是代理对象再次生成代理对象,特殊的是代理对象的target属性

  • 另外配合Invocation类中的proceed方法形成责任链路的调用,这样就可以在我们执行sql的前后,做一些特殊的自定义的事情了。

这篇关于Mybatis链路分析:JDK动态代理和责任链模式的应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1120554

相关文章

将Mybatis升级为Mybatis-Plus的详细过程

《将Mybatis升级为Mybatis-Plus的详细过程》本文详细介绍了在若依管理系统(v3.8.8)中将MyBatis升级为MyBatis-Plus的过程,旨在提升开发效率,通过本文,开发者可实现... 目录说明流程增加依赖修改配置文件注释掉MyBATisConfig里面的Bean代码生成使用IDEA生

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

C#如何动态创建Label,及动态label事件

《C#如何动态创建Label,及动态label事件》:本文主要介绍C#如何动态创建Label,及动态label事件,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#如何动态创建Label,及动态label事件第一点:switch中的生成我们的label事件接着,

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2