框架源码专题:Spring是如何集成Mybatis的?Spring怎么管理Mapper接口的动态代理
文章目录
- 1. Spring集成Mybatis代码示例
- 2. Spring 如何解析Mybatis配置文件
- 3. Spring是怎么管理Mapper接口的动态代理的
- 4. Spring整合Mybatis源码大致流程
1. Spring集成Mybatis代码示例
Spring在集成Mybatis时,使用SqlSessionFactoryBean
来完成Configuration
的解析,代码如下:
@EnableTransactionManagement
@Configuration
@MapperScan(basePackages = {"com.tuling.mapper"})
@ComponentScan(basePackages = {"com.tuling"})
@Repository
public class MyBatisConfig { // =====> spring.xml//用SqlSessionFactoryBean来代替Mybatis中解析Configuration的过程@Bean public SqlSessionFactoryBean sqlSessionFactory( ) throws IOException {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();//设置数据源factoryBean.setDataSource(dataSource());// 设置 MyBatis 配置文件路径factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));// 设置 SQL 映射文件路径factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));factoryBean.setTypeAliases(User.class);return factoryBean;}//数据源public DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setUsername("root");dataSource.setPassword("123456");dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis_example");return dataSource;}
2. Spring 如何解析Mybatis配置文件
Spring 是根据bean对象SqlSessionFactoryBean
来解析xml文件到Configuration
类中的,首先来看一下SqlSessionFactoryBean
这个类:
//SqlSessionFactoryBean 实现了FactoryBean 和 InitializingBean
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {//内部封装了Configuration 类private Configuration configuration;private Resource[] mapperLocations;private DataSource dataSource;//类型处理器private TypeHandler<?>[] typeHandlers;private String typeHandlersPackage;//类型别名private Class<?>[] typeAliases;private String typeAliasesPackage;。。。。。。 不一一列举
可以看到SqlSessionFactoryBean内部封装了Configuration类,
实现了FactoryBean,会创建getObject中返回的单例sqlSessionFactory对象,这个对象就是我们Mybatis中最开始的DefaultSqlSessionFactory对象!
@Overridepublic SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {//如果sqlSessionFactory 为空,则先创建sqlSessionFactory !afterPropertiesSet();}return this.sqlSessionFactory;}
还实现了InitializingBean,InitializingBean中的afterPropertiesSet方法会在类初始化完成后调用,而上边的getObject() 方法在获取对象时会调用这个方法生成sqlSessionFactory
@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");//创建sqlSessionFactory 的方法this.sqlSessionFactory = buildSqlSessionFactory();}
核心方法是buildSqlSessionFactory,在这个方法中,大部分都是为Configuration对象赋值!解析过程与Mybatis解析过程类似!最后通过建造者模式返回sqlSessionFactory的实现
DefaultSqlSessionFactory!
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {//局部变量ConfigurationConfiguration configuration;.......if (this.objectFactory != null) {configuration.setObjectFactory(this.objectFactory);}if (this.vfs != null) {configuration.setVfsImpl(this.vfs);}//设置类型别名if (!isEmpty(this.typeAliases)) {for (Class<?> typeAlias : this.typeAliases) {configuration.getTypeAliasRegistry().registerAlias(typeAlias);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Registered type alias: '" + typeAlias + "'");}}}//添加插件if (!isEmpty(this.plugins)) {for (Interceptor plugin : this.plugins) {configuration.addInterceptor(plugin);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Registered plugin: '" + plugin + "'");}}}//设置类型处理器if (!isEmpty(this.typeHandlers)) {for (TypeHandler<?> typeHandler : this.typeHandlers) {configuration.getTypeHandlerRegistry().register(typeHandler);if (LOGGER.isDebugEnabled()) {LOGGER.debug("Registered type handler: '" + typeHandler + "'");}}}//缓存if (this.cache != null) {configuration.addCache(this.cache);}//事务管理器//SpringManagedTransactionFactory是新定义的事务管理器,它使用Spring事务中的dataSource ,从而达到跟事务集成if (this.transactionFactory == null) {this.transactionFactory = new SpringManagedTransactionFactory();}//环境configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));....... 省略//建造者模式 返回 DefaultSqlSessionFactoryreturn this.sqlSessionFactoryBuilder.build(configuration);}
注意:
- Spring集成mybatis,在处理事务时,事务工厂会使用一个新的
new SpringManagedTransactionFactory
- 而不是MyBatis之前的
ManagedTransactionFactory
, 这个SpringManagedTransactionFactory
会使用Spring事务同步管理器TransactionSynchronizationManager
中的dataSource , 从而达到跟事务集成
3. Spring是怎么管理Mapper接口的动态代理的
Spring与Mybatis
整合的最主要目的就是:把Mapper
接口的代理对象放入Spring容器,在使用时能够像使用一个普通的bean一样去使用这个代理对象,比如能被@Autowired
自动注入!
整合结果,能够像下面的方式一样使用代理对象
@Component
public class UserService {@Autowiredprivate UserMapper userMapper;public User getUserById(Integer id) {return userMapper.selectById(id);}
}
UserService
中的userMapper
属性就会被自动注入为Mybatis中的代理对象。如果你基于一个已经完成整合的项目去调试即可发现,userMapper
的类型为:org.apache.ibatis.binding.MapperProxy@41a0aa7d
。证明确实是Mybatis中的代理对象。
那么问题来了:
要想使用@AutoWired
注入xxxMapper
代理对象,Spring容器中必须要存在才行啊? 那么Spring是怎么把不同的Mapper
代理对象作为一个bean放入容器中呢?
- 猜想一:Spring中可以通过注解把类加入容器,比如@Controller、@Service、@Component等等,如果为Mapper接口加上@Component注解,是否可以放入Spring容器?
答案:不能。
要验证这个猜想,我们需要对 Spring的bean生成过程 有一个了解。Spring
启动过程中,大致会经过如下步骤去生成bean:- 扫描
@ComponentScan
中指定的包路径下的class文件 - 根据class信息判断是否符合生成对应的
BeanDefinition
的条件(接口和抽象类不符合条件),如果符合则生成对应的BeanDefinition
- 在此处,程序员可以利用某些机制去修改
BeanDefinition
,实现扩展 - 根据
BeanDefinition
中的class信息反射生成bean实例 - 把生成的
bean
实例放入Spring容器中
- 扫描
由此步骤可见,要想生成bean对象,首先需要有BeanDefinition。由于Mapper接口上加@Component在第2步就会被过滤掉,无法生成BeanDefinition,更无法生成实例。
猜想二:使用
@Bean
把代理类注册进去,在@Bean代码中生成Mapper接口的动态代理!这种方式其实是可行的,因为Spring在进行bean实例化时有两种方式。
- ①:带有
@Component
等注解的类直接利用class
反射生成实例 - ②:带有
@Bean
注解的则会使用工厂的方式生成实例
- ①:带有
显然第二种猜想是可行的,但是存在一个问题,一个项目可能有上百个Mapper,难道每一个都要写一个@Bean生成代理类?这岂不是累死个人!!Spring绝对不是靠这种方式整合的!
Sping的解决方案:
先回到猜想一中的思路,在Mapper接口上加@Component
注解时,Spring会在扫描@CompopnentScan
指定的路径时,过滤掉接口、抽象类,不为他们生成BeanDefinition
,这就导致了Mapper
接口无法实例化!
//判断是否是顶级类、非接口类、非抽象类等等,整合Mybatis时,需要重写这个方法,让Mapper接口也加入集合!if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}//如果不是顶级类、接口类、抽象类,则加入到集合中!!!candidates.add(sbd);}else {//如果是顶级类、接口类、抽象类,则不予处理!!!if (debugEnabled) {logger.debug("Ignored because not a concrete top-level class: " + resource);}}======== isCandidateComponent 判断是否是顶级类、非接口类、非抽象类等等=======protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {AnnotationMetadata metadata = beanDefinition.getMetadata();/*** 判断是否是顶级类、非接口类、非抽象类等等*/return (metadata.isIndependent() && (metadata.isConcrete() ||(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));}
然而,Spring在整合Mybatis时,为了让Mapper接口注册成BeanDefinition,重写了isCandidateComponent 方法!!,他让这个方法忽略了接口,让接口也可以被扫描到。
但是接口也可以被扫描到也不能改变什么啊,因为Mapper接口只是一个接口,还没有为他生成代理类呢!!Spring为了解决这个问题,使用了FactoryBean的getObject方法进行了偷天换日, 在Mapper
接口的BeanDefinition
中设置其beanClass
为Factorybean.class
,这样每个Mapper
接口在生成实例的时候生成的是Factorybean
的getObject
方法中返回的代理类了!!
再通过自定义类实现bean
工厂后置处理器BeanDefinitionRegistryPostProcessor
,重写postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
方法,修改成带有Factorybean.class
的BeanDefinition
,这样就会按照我们自定义的BeanDefinition
去生成对象! 这就完美解决了问题。
伪代码如下:
//扫描到的 MappersSet mappers = new HashSet();//遍历 Mappersfor (Object mapper : mappers) {//新建一个bean定义RootBeanDefinition beanDefinition = new RootBeanDefinition();//偷天换日,设置MyfactoryBean的getObject方法返回的对象为mapper的ClassbeanDefinition.setBeanClass(MyfactoryBean.class);//每一次循环都为当前mapper创建动态代理beanDefinition.getPropertyValues().add("mapperInterFace",mapper.class);//最后注册成bean定义registry.registerBeanDefinition("userMapper",beanDefinition);}
4. Spring整合Mybatis源码大致流程
- spring会重写
isCandidateComponent
方法,来扫描到所有的mapper
接口,并将所有mapper
的 bean定义中的class
类型指向MapperFactoryBean
; - Spring在创建
UserServiceImpl
实例的时候,发现其内部有@Autowired
了UserMapper
接口,那么就会去spring
容器获取UserMapper
实例,没有则进行创建 - 创建
UserMapper
实例的时候,根据bean定义创建的实例 实际上是MapperFactoryBean
实例,然后再利用MapperFactoryBean
的getObject
方法获取mapper的代理实例(调用MapperFactoryBean的getObject方法,mybatis会利用jdk的动态代理创建mapper代理对象);
MapperFactoryBean 类如下:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {private Class<T> mapperInterface;private boolean addToConfig = true;public MapperFactoryBean() {}public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}protected void checkDaoConfig() {super.checkDaoConfig();Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");Configuration configuration = this.getSqlSession().getConfiguration();if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {try {configuration.addMapper(this.mapperInterface);} catch (Exception var6) {this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);throw new IllegalArgumentException(var6);} finally {ErrorContext.instance().reset();}}}// getObject获取对应Mapper的代理类public T getObject() throws Exception {return this.getSqlSession().getMapper(this.mapperInterface);}public Class<T> getObjectType() {return this.mapperInterface;}public boolean isSingleton() {return true;}public void setMapperInterface(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}public Class<T> getMapperInterface() {return this.mapperInterface;}public void setAddToConfig(boolean addToConfig) {this.addToConfig = addToConfig;}public boolean isAddToConfig() {return this.addToConfig;}
}
框架源码专题:Spring是如何集成Mybatis的?Spring怎么管理Mapper接口的动态代理相关推荐
- 框架源码专题:Mybatis启动和执行流程、源码级解析
文章目录 1. Mybatis 启动流程 步骤一: 把xml配置文件解析成Configuration类 步骤二: 创建SqlSession会话 mybatis的三种执行器 步骤三: 在sqlSessi ...
- 框架源码专题:springIOC的加载过程,bean的生命周期,结合spring源码分析
文章目录 1.BeanFactory和ApplicationContext的区别? 2. IOC与 Bean的加载过程 ①:初始化容器DefaultListableBeanFactory ②:创建读取 ...
- 框架源码专题:Spring的Aop实现原理,Spring AOP 与 AspectJ 的关系
文章目录 1. Spring AOP 与 AspectJ 的关系 2. JDK和Cglib动态代理的区别 3. Spring AOP应用案例 4. Spring AOP有几种配置方式? 5. Spri ...
- 框架源码专题:Spring声明式事务Transactional的原理
文章目录 1. @Transactional的使用 2. spring事务的原理 2.1 开启事务,注册bean的后置处理器和相关bean对象,封装Advisor 2.2 匹配并创建动态代理 2.3 ...
- 框架源码专题:Spring的事件监听、发布机制 ApplicationListener
文章目录 1.Spring内置事件 2.自定义事件 3.事件监听器 4.事件发布 publishEvent 4.Spring事件原理 5. 面试题:怎么样可以在所有Bean创建完后做扩展代码? 6. ...
- 框架源码专题:Spring是如何解决循环依赖的?
文章目录 1.什么是循环依赖? 2.解决循环依赖思路 3. 使用了三级缓存还有什么问题?怎么解决的? 4. 手写伪代码解决缓存依赖 5. 二级缓存能否解决循环依赖,三级缓存存在的意义 6. Sprin ...
- 框架源码专题:Mybatis的一级缓存、二级缓存是什么?有什么作用?
文章目录 1. Mybatis中缓存的作用 2. 一级缓存 3. 二级缓存 4. 一级缓存和二级缓存的区别 5. 通过代码观察Mybatis缓存工作的全过程 1. Mybatis中缓存的作用 首先缓存 ...
- Spring框架----基于接口的动态代理
由我们前面对代理的分析 对代理的分析 有生产商,销售人员和消费者这3个角色,销售人员是中间代理商.代理销售和售后的工作. 而在刚开始的时候,我们并没有销售人员.根据这样的思路,我们写出以下代码 接口I ...
- Spring 框架源码(二) 事务Transaction源码深度解析
目录 一.基于xml形式开启Transaction 1. 创建数据库user 2. 创建一个maven 项目 3. 通过xml形式配置事务 1) 创建Spring 命名空间 2) 开启事务配置 3) ...
最新文章
- 新手如何学习云计算大数据,云计算的学习路线
- 两段用来启动/重启Linux下Tomcat的Perl脚本
- JAVA基础知识要点
- ext3分区修复linux,linux – 如何从损坏的ext3分区恢复数据?
- 中科院自动化所:最新视觉-语言预训练综述
- apt-mirror 校验错误文件处理
- “云计算+DevOps”的正确打开方式
- LeetCode之Palindrome Number(回文数)
- Drools 6.2.0.Final发布
- 点云技术无中生有100篇(一)-无人机航测如何正确布设像控点
- UVA 473——Raucous Rockers
- Java可变引用,Java – 对可变对象的易失性引用 – 对对象的字段的更新对所有线程都是可见的...
- 信息系统项目管理04——项目整体管理
- java设计模式刘伟模拟题答案,灵魂拷问
- PAT甲级1004 (DFS,树的父子节点)
- Bailian4140 方程求解【二分法】
- [leetcode]_Climbing Stairs
- FreeSwitch视频会议,4路以上CPU占用暴增的原因
- Android monkey 压力测试
- 如何用python的turtle画五角星_使用Python的turtle模块画五角星
热门文章
- centos环境访问php显示源码,CentOS 6.8 搭建LNAMP环境(五)- PHP7源码安装Redis和Redis拓展...
- winsock使用java编写_利用Socket进行Java网络编程(一)
- mysql hdfs_MySQL数据库与HDFS的实时数据同步
- ios 各种锁的使用性能比较
- iPhone 诈骗又出新招,别看见弹窗就输密码
- 定义软件定义的存储市场
- awk命令输出单引号
- rsync和inotify实时同步配置 exclude排除多个文件夹
- HttpRuntime.Cache的使用经验
- drupal 字符串替换符号 @ % !