文章目录

  • 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:

    1. 扫描@ComponentScan中指定的包路径下的class文件
    2. 根据class信息判断是否符合生成对应的BeanDefinition的条件(接口和抽象类不符合条件),如果符合则生成对应的BeanDefinition
    3. 在此处,程序员可以利用某些机制去修改BeanDefinition,实现扩展
    4. 根据BeanDefinition中的class信息反射生成bean实例
    5. 把生成的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中设置其beanClassFactorybean.class,这样每个Mapper接口在生成实例的时候生成的是FactorybeangetObject方法中返回的代理类了!!

再通过自定义类实现bean工厂后置处理器BeanDefinitionRegistryPostProcessor,重写postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 方法,修改成带有Factorybean.classBeanDefinition,这样就会按照我们自定义的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源码大致流程

  1. spring会重写isCandidateComponent方法,来扫描到所有的mapper接口,并将所有mapper 的 bean定义中的class类型指向MapperFactoryBean
  2. Spring在创建UserServiceImpl实例的时候,发现其内部有@AutowiredUserMapper接口,那么就会去spring容器获取UserMapper实例,没有则进行创建
  3. 创建UserMapper实例的时候,根据bean定义创建的实例 实际上是MapperFactoryBean实例,然后再利用MapperFactoryBeangetObject方法获取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接口的动态代理相关推荐

  1. 框架源码专题:Mybatis启动和执行流程、源码级解析

    文章目录 1. Mybatis 启动流程 步骤一: 把xml配置文件解析成Configuration类 步骤二: 创建SqlSession会话 mybatis的三种执行器 步骤三: 在sqlSessi ...

  2. 框架源码专题:springIOC的加载过程,bean的生命周期,结合spring源码分析

    文章目录 1.BeanFactory和ApplicationContext的区别? 2. IOC与 Bean的加载过程 ①:初始化容器DefaultListableBeanFactory ②:创建读取 ...

  3. 框架源码专题:Spring的Aop实现原理,Spring AOP 与 AspectJ 的关系

    文章目录 1. Spring AOP 与 AspectJ 的关系 2. JDK和Cglib动态代理的区别 3. Spring AOP应用案例 4. Spring AOP有几种配置方式? 5. Spri ...

  4. 框架源码专题:Spring声明式事务Transactional的原理

    文章目录 1. @Transactional的使用 2. spring事务的原理 2.1 开启事务,注册bean的后置处理器和相关bean对象,封装Advisor 2.2 匹配并创建动态代理 2.3 ...

  5. 框架源码专题:Spring的事件监听、发布机制 ApplicationListener

    文章目录 1.Spring内置事件 2.自定义事件 3.事件监听器 4.事件发布 publishEvent 4.Spring事件原理 5. 面试题:怎么样可以在所有Bean创建完后做扩展代码? 6. ...

  6. 框架源码专题:Spring是如何解决循环依赖的?

    文章目录 1.什么是循环依赖? 2.解决循环依赖思路 3. 使用了三级缓存还有什么问题?怎么解决的? 4. 手写伪代码解决缓存依赖 5. 二级缓存能否解决循环依赖,三级缓存存在的意义 6. Sprin ...

  7. 框架源码专题:Mybatis的一级缓存、二级缓存是什么?有什么作用?

    文章目录 1. Mybatis中缓存的作用 2. 一级缓存 3. 二级缓存 4. 一级缓存和二级缓存的区别 5. 通过代码观察Mybatis缓存工作的全过程 1. Mybatis中缓存的作用 首先缓存 ...

  8. Spring框架----基于接口的动态代理

    由我们前面对代理的分析 对代理的分析 有生产商,销售人员和消费者这3个角色,销售人员是中间代理商.代理销售和售后的工作. 而在刚开始的时候,我们并没有销售人员.根据这样的思路,我们写出以下代码 接口I ...

  9. Spring 框架源码(二) 事务Transaction源码深度解析

    目录 一.基于xml形式开启Transaction 1. 创建数据库user 2. 创建一个maven 项目 3. 通过xml形式配置事务 1) 创建Spring 命名空间 2) 开启事务配置 3) ...

最新文章

  1. 新手如何学习云计算大数据,云计算的学习路线
  2. 两段用来启动/重启Linux下Tomcat的Perl脚本
  3. JAVA基础知识要点
  4. ext3分区修复linux,linux – 如何从损坏的ext3分区恢复数据?
  5. 中科院自动化所:最新视觉-语言预训练综述
  6. apt-mirror 校验错误文件处理
  7. “云计算+DevOps”的正确打开方式
  8. LeetCode之Palindrome Number(回文数)
  9. Drools 6.2.0.Final发布
  10. 点云技术无中生有100篇(一)-无人机航测如何正确布设像控点
  11. UVA 473——Raucous Rockers
  12. Java可变引用,Java – 对可变对象的易失性引用 – 对对象的字段的更新对所有线程都是可见的...
  13. 信息系统项目管理04——项目整体管理
  14. java设计模式刘伟模拟题答案,灵魂拷问
  15. PAT甲级1004 (DFS,树的父子节点)
  16. Bailian4140 方程求解【二分法】
  17. [leetcode]_Climbing Stairs
  18. FreeSwitch视频会议,4路以上CPU占用暴增的原因
  19. Android monkey 压力测试
  20. 如何用python的turtle画五角星_使用Python的turtle模块画五角星

热门文章

  1. centos环境访问php显示源码,CentOS 6.8 搭建LNAMP环境(五)- PHP7源码安装Redis和Redis拓展...
  2. winsock使用java编写_利用Socket进行Java网络编程(一)
  3. mysql hdfs_MySQL数据库与HDFS的实时数据同步
  4. ios 各种锁的使用性能比较
  5. iPhone 诈骗又出新招,别看见弹窗就输密码
  6. 定义软件定义的存储市场
  7. awk命令输出单引号
  8. rsync和inotify实时同步配置 exclude排除多个文件夹
  9. HttpRuntime.Cache的使用经验
  10. drupal 字符串替换符号 @ % !