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的前后,做一些特殊的自定义的事情了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/53066.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac 安装Hadoop教程(HomeBrew安装)

1. 引言 本教程旨在介绍在Mac 电脑上安装Hadoop&#xff0c;便于编程开发人员对大数据技术的熟悉和掌握。 2.前提条件 2.1 安装JDK 想要在你的Mac电脑上安装Hadoop&#xff0c;你必须首先安装JDK。具体安装步骤这里就不详细描述了。你可参考Mac 安装JDK8。 2.2 配置ssh环境…

【CVPR‘24】DeCoTR:使用 2D 和 3D 注意力增强深度补全

DeCoTR: Enhancing Depth Completion with 2D and 3D Attentions DeCoTR: Enhancing Depth Completion with 2D and 3D Attentions 中文解析摘要介绍方法方法3.1 问题设置3.2 使用高效的 2D 注意力增强基线3.3 3D中的特征交叉注意力点云归一化位置嵌入3.4 捕捉 3D 中的全局上下…

给鼠标一个好看的指针特效 鼠标光标如何修改形状?

许多爱美的小伙伴们都想着如何给自己的电脑打扮一下&#xff0c;用各种各样的途径来美化我们的电脑。今天我们给大家分享一下&#xff0c;如何美化鼠标效果&#xff0c;给鼠标指针修改成一个非常好看的形状~ 一起来看几组鼠标的效果&#xff0c;小编我给大家做了个录屏&#x…

linux文件——用户缓冲区——概念深度探索、IO模拟实现

前言&#xff1a;本篇文章主要讲解文件缓冲区。 讲解的方式是通过抛出问题&#xff0c; 然后通过分析问题&#xff0c; 将缓冲区的概念与原理一步一步地讲解。同时&#xff0c; 本节内容在最后一部分还会带友友们模拟实现一下c语言的printf&#xff0c; fprintf接口&#xff0c…

依托自研力量,给共享集群存储服务一个优选

YashanDB共享集群有三大关键组件&#xff0c;崖山集群服务&#xff08;YCS&#xff09;、崖山集群文件系统&#xff08;YFS&#xff09;、DB组件。上一篇共享集群系列文章《为何共享集群的高可用能力被频频称赞&#xff0c;它的机制有何不同&#xff1f;》深入解析了关键组件的…

NVIDIA RTX 50系列大爆料:功耗飙升600W,性能直逼RTX 4090 1.?倍,你准备好了吗?

在科技圈的万众瞩目下&#xff0c;知名硬件爆料大神Kopite7kimi再次为我们揭开了NVIDIA下一代GeForce RTX系列——“Blackwell”阵容的神秘面纱。这次&#xff0c;关于新显卡的功耗信息不再是模糊的概念&#xff0c;而是实实在在的数字&#xff0c;让人不禁对即将到来的性能飞跃…

ELK学习笔记(一)——使用K8S部署ElasticSearch8.15.0集群

一、下载镜像 #1、下载官方镜像 docker pull elasticsearch:8.15.0 #2、打新tag docker tag elasticsearch:8.15.0 192.168.9.41:8088/new-erp-common/elasticsearch:8.15.0 #3、推送到私有仓库harbor docker push 192.168.9.41:8088/new-erp-common/elasticsearch:8.15.0二、…

Python3.8绿色便携版安装版制作

Python 的绿色便携版有两种&#xff1a;官方 Embeddable 版本&#xff08;嵌入式版&#xff09;&#xff1b;安装版制作的绿色版。Embeddable 版适用于需要将 Python 集成到其他应用程序或项目中的情况,它不包含图形界面的安装程序&#xff0c;只提供了 Python 解释器和必要的库…

排查SQL Server中的内存不足及其他疑难问题

文章目录 引言I DMV 资源信号灯资源信号灯 DMV sys.dm_exec_query_resource_semaphores( 确定查询执行内存的等待)查询性能计数器什么是内存授予?II DBCC MEMORYSTATUS 查询内存对象III DBCC 命令释放多个 SQL Server 内存缓存 - 临时度量值IV 等待资源池 %ls (%ld)中的内存…

Matlab R2022b使用Camera Calibrator工具箱张正友标定法进行相机标定附带标定前后对比代码

打开Camera Calibrator 在这添加你拍摄的图片 根据你每个方块的实际边长填写&#xff0c;我是15mm。 通俗一点&#xff0c;要k3就选3 Coefficients&#xff0c;否则为0&#xff1b;要p1、p2就选Tangential Distortion。然后进行计算。 可以点击右侧误差高的选中图像进行移…

AI-Talk开发板之LED

一、说明 AI-Talk开发板上有一颗用户LED&#xff0c;连接在CH32 PA2管脚&#xff0c;低电平亮&#xff0c;高电平灭。 相关电路图如下&#xff1a; 二、工程 1、创建项目 进入snap/examples/目录&#xff0c;执行创建项目的命令&#xff1a; lisa zep create ? 选择sam…

第66期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

C++:关于反向迭代器的学习分享

前言&#xff1a; 小编仅是一位初学者&#xff0c;所以对于C的理解有限&#xff0c;文章大概率会出现表达不清楚可能也只是因为小编不知道如何更好表达&#xff0c;本文章仅作为一个学习的总结分享。 反向迭代器的概念 反向迭代器故名思意解释反向的迭代器&#xff0c;与正向迭…

银行接口测试的具体流程及内容?

银行接口测试的具体流程及内容可以概括为以下几个关键步骤&#xff1a; 一、测试前准备 接口测试计划制定&#xff1a; 确定测试目标、范围、人员分工、时间计划等。 编写接口测试计划文档&#xff0c;明确测试策略、测试方法、测试工具等。 接口文档解析&#xff1a; 深入理…

边缘智能网关 P1600:智慧城市的创新引擎

智慧城市&#xff08;Smart City&#xff09;是以发展更科学、管理更高效、生活更美好为目标&#xff0c;以信息技术和通信技术为支撑&#xff0c;通过透明、充分的信息获取&#xff0c;广泛、安全的信息传递和有效、科学的信息处理&#xff0c;提高城市运行效率&#xff0c;改…

数据库管理-第238期 23ai:全球分布式数据库-架构与组件(20240904)

数据库管理238期 2024-09-04 数据库管理-第238期 23ai&#xff1a;全球分布式数据库-架构与组件&#xff08;20240904&#xff09;1 架构图2 分片数据库与分片3 Shard Catalog4 Shard Director5 Global Service6 管理界面总结 数据库管理-第238期 23ai&#xff1a;全球分布式数…

Python案例 | 四阶龙格库塔法简介

1.引言 在数值分析中&#xff0c;龙格-库塔法&#xff08;Runge-Kutta methods&#xff09;是用于非线性常微分方程的解的重要的一类隐式或显式迭代法。这些技术由数学家卡尔龙格和马丁威尔海姆库塔于1900年左右发明。 龙格-库塔(Runge-Kutta)方法是一种在工程上应用广泛的高…

HALCON与LabVIEW的联合编程 视觉与控制结合

HALCON与LabVIEW的联合编程在工业自动化和视觉检测领域中越来越受到重视。通过将HALCON的强大图像处理能力与LabVIEW的灵活控制功能相结合&#xff0c;工程师们可以开发出高效且精确的自动化系统。这种整合不仅提高了系统的整体性能&#xff0c;还简化了开发流程。本文将详细介…

人工智能造福公众:未来一片光明

作者&#xff1a;来自 Elastic Peter Dutton 我们如何衡量人工智能对政府的影响&#xff1f;毫无疑问&#xff0c;人工智能将为运营流程和决策带来的好处已被广泛讨论 —— 从自动化工作流程到节省成本再到减少重复工作。 但对于以服务公众为目标的组织来说&#xff0c;人工智…

2024 年 Web3 融资情况解析:公售项目占比超八成,散户抱团取暖

作者&#xff1a;Jasper De Maere&#xff0c;Outlier Ventures 编译&#xff1a;J1N&#xff0c;Techub News 自 2024 年 3 月以来&#xff0c; 加密货币市场出现大幅度回调&#xff0c; 多数 Altcoin &#xff08;除了比特币、以太坊等以外的代币&#xff09;&#xff0c;特…