前言

  开心一刻   

    中韩两学生辩论。
    中:端午节是属于谁的?
    韩:韩国人!
    中:汉字是谁发明的?
    韩:韩国人!
    中:中医是属于谁的?
    韩:韩国人!
    中:那中国人到底发明过什么?
    韩:韩国人!

前情回顾

  Mybatis源码解析 - mapper代理对象的生成,你有想过吗,我们讲到了mybatis操作数据库的流程:先创建SqlSessionFactory,然后创建SqlSession,然后再创建获取mapper代理对象,最后利用mapper代理对象完成数据库的操作;Mapper代理对象的创建,利用的是JDK的动态代理,InvocationHandler是MapperProxy,后续Mapper代理对象方法的执行都会先经过MapperProxy的invoke方法。

  但是,此时SqlSessionFactory的创建、SqlSession的创建以及mapper代理对象的获取都是我们手动操作的,实际应用中,mybatis往往也不会单独使用,绝大多数都是集成在spring中,也就是说我们无需手动去管理mybatis相关对象的生命周期,全部都由spring容器统一管理,那么spring是什么时候在哪创建的mybatis的相关对象的呢?尤其是mapper代理对象MapperProxy的创建

Springboot集成mybatis

  当springboot(其实还是spring)集成mybatis后,mybatis的对象是交给spring容器管理的,只会实例化一次,然后伴随着spring容器一直存在,直到spring容器销毁

  自动配置:MybatisAutoConfiguration

    Mybatis的自动配置类:MybatisAutoConfiguration,至于如何加载此类,可参考:spring-boot-2.0.3启动源码篇一 - SpringApplication构造方法

    MybatisAutoConfiguration会被当做配置类被spring解析,我们来看看spring容器会从此配置类中解析到什么

    创建了SqlSessionFactory实例(实际类型:DefaultSqlSessionFactory),并注册到了spring容器;此时我们应该还注意到

  @Import({ AutoConfiguredMapperScannerRegistrar.class })

    AutoConfiguredMapperScannerRegistrar继承了ImportBeanDefinitionRegistrar(注意看类注释,有兴许的可以更深入的研究下),那么它的registerBeanDefinitions也会被调用

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { logger.debug("Searching for mappers annotated with @Mapper"); ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
} // 获取启动类所在的包,如:com.lee.shiro,会作为扫描开始的base package,一般只会有一个,但支持多个
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
for (String pkg : packages) {
logger.debug("Using auto-configuration base package '{}'", pkg);
}
} scanner.setAnnotationClass(Mapper.class); // 设置扫谁,Mapper注解是被扫描对象
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(packages)); // 扫描所有mapper,进行bean定义处理
} catch (IllegalStateException ex) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
}
}

    以我们启动类所在的包(com.lee.shiro)为基包,扫描所有的mapper,然后修改所有mapper在spring容器中的bean定义,将mapper的beanClass全部指向了MapperFactoryBean

  mapper代理对象的创建:MapperFactoryBean

    MapperFactoryBean继承SqlSessionDaoSupport,SqlSessionDaoSupport有两个方法用来设置SqlSession

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
} public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}

    可以看到SqlSession的实际类型是:SqlSessionTemplate,SqlSessionTemplate在MybatisAutoConfiguration以@Bean方式创建的

    Spring在创建Service实例:UserServiceImpl的时候,发现依赖mapper(可能还有其他的实例依赖mapper),那么就会去spring容器获取mapper实例,没有则进行创建,然后注入进来(依赖注入);具体创建过程如下

if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
// 创建mapper对象,beanName:com.lee.shiro.mapper.UserMapper,创建出来的实例实际上是MapperFactoryBean类型
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
// 获取给定bean实例的对象,如果是FactoryBean,则获取bean实例本身或其创建的对象
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

  

    因为Spring在mapper扫描的时候,将所有mapper bean定义中的beanClass设置成了MapperFactoryBean(继承了FactoryBean),所以通过createBean方法创建的mapper实例实际上是MapperFactoryBean对象,然后通过

bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { // Don't let calling code try to dereference the factory if the bean isn't a factory.
// isFactoryDereference方法判断name中是否有&字符
if (BeanFactoryUtils.isFactoryDereference(name)) {
if (beanInstance instanceof NullBean) {
return beanInstance;
}
if (!(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
}
} // Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
// 此时的beanInstance可能是一个普通bean,也可能是一个FactoryBean
// 如果是一个FactoryBean,那么就用它创建想要的bean实例
// 此if表示,如果beanInstance是普通bean,或者本来就想要FactoryBean实例,则直接返回beanInstance
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
} Object object = null;
if (mbd == null) {
object = getCachedObjectForFactoryBean(beanName);
}
// 此时表明beanInstance是一个FactoryBean,并且不是想要FactoryBean实例
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
// 通过FactoryBean实例创建我们想要的实例
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}

    获取真正想要的bean实例,如果beanInstance是普通bean,或者本来就想要FactoryBean实例(beanName中有&),那么直接返回beanInstance,否则用FactoryBean实例来创建我们想要的实例对象。说回来就是会调用MapperFactoryBean的getObject()方法来获取Mapper的代理对象

    后续流程就可以参考:Mybatis源码解析 - mapper代理对象的生成,你有想过吗

  至此,前情回顾中的问题也就清晰了

总结

  1、自动配置的过程中,spring会扫描所有的mapper,并将所有mapper bean定义中的beanClass指向MapperFactoryBean;

  2、创建mapper实例的时候,根据bean定义创建的实例实际上是MapperFactoryBean实例,然后再利用MapperFactoryBean获取mapper实例(调用MapperFactoryBean的getObject方法,mybatis会利用jdk的动态代理创建mapper代理对象);

  3、对比Mybatis源码解析 - mapper代理对象的生成,你有想过吗,其实就是将我们手动创建的过程通过自动配置,将创建过程交给了spring;

  4、关于spring的FactoryBean,请看看这篇:Spring拓展接口之FactoryBean,我们来看看其源码实现

springboot集成下,mybatis的mapper代理对象究竟是如何生成的的更多相关文章

  1. Mybatis源码解析 - mapper代理对象的生成,你有想过吗

    前言 开心一刻 本人幼教老师,冬天戴帽子进教室,被小朋友看到,这时候,有个小家伙对我说:老师你的帽子太丑,赶紧摘了吧.我逗他:那你好好学习,以后给老师买个漂亮的?这孩子想都没想立刻回答:等我赚钱了,带 ...

  2. springboot学习笔记:11.springboot+shiro+mysql+mybatis(通用mapper)+freemarker+ztree+layui实现通用的java后台管理系统(权限管理+用户管理+菜单管理)

    一.前言 经过前10篇文章,我们已经可以快速搭建一个springboot的web项目: 今天,我们在上一节基础上继续集成shiro框架,实现一个可以通用的后台管理系统:包括用户管理,角色管理,菜单管理 ...

  3. Mybatis的mapper代理开发方法

    一.开发规范 1.映射文件中的namespase等于mapper接口类路径 2.statement的id与mapper中的方法名一致 3.让mapper的接口方法输入参数类型与statement中的p ...

  4. mybatis——使用mapper代理开发方式

    ---------------------------------------------------------------generatorConfig.xml------------------ ...

  5. Mybatis的mapper代理开发dao方法

    看完了之前的mybatis原始的dao开发方法是不是觉得有点笨重,甚至说没有发挥mybatis 作为一个框架的优势.总结了一下,原始的dao方法有以下几点不足之处 dao接口实现方法中存在大量的模板方 ...

  6. Mybatis非mapper代理配置

    转: Mybatis非mapper代理配置 2017年04月26日 20:13:48 待长的小蘑菇 阅读数:870   版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog. ...

  7. mybatis关于ORM的使用以及设计(二)[DaoInterface 转换 Mapper代理对象]

    第一节中,分析了Mybatis的ORM框架的初始化,这篇来分析SQL执行过程中,对象->SQL是如何转换的 其中包含两种映射思想 ①DAO接口->Mapper实例 ②执行DAO的方法时,参 ...

  8. Spring+SpringMVC+Mybatis大整合(SpringMVC采用REST风格、mybatis采用Mapper代理)

    整体目录结构: 其中包下全部是采用mybatis自动生成工具生成. mybatis自动生成文件 <?xml version="1.0" encoding="UTF- ...

  9. springboot学习笔记:8. springboot+druid+mysql+mybatis+通用mapper+pagehelper+mybatis-generator+freemarker+layui

    前言: 开发环境:IDEA+jdk1.8+windows10 目标:使用springboot整合druid数据源+mysql+mybatis+通用mapper插件+pagehelper插件+mybat ...

随机推荐

  1. windows10计划任务启动bat执行jar打包的jar文件

    今天公司要用bat文件执行jar打包的java文件,运行没项目的程序,并且用任务计划开机自启,今天记录下坑 系统:win10 一.编写.bat执行用jar打包的jar文件 @echo off java ...

  2. 用generator改写ajax

    function request(url) { // this is where we're hiding the asynchronicity, // away from the main code ...

  3. js的算法题

    1.统计一个字符串中出现最多的字母 给出一个字符串,统计出现次数最多的字母.如:“wqeqwhixswiqhdxsq”,其中出现最多的是q. js算法的实现 function findMax(str) ...

  4. linux crontab 执行mysqldump全局备份为空

    今天遇到个问题,在定时备份时 去查看备份文件,发现大小竟然为0,执行 备份sh文件备份, 备份的sql文件大小正常.试了几种办法. 最终解决办法: 问题原因: 因为我设置的环境变量 就直接在sh中 使 ...

  5. 封装axios

    import axios from 'axios' // import store from '@/vuex/store.js' import router from '../router' impo ...

  6. Ubuntu上安装使用WeChat、TIM

    WeChat可以直接到软件商店安装,不过是网页版...(其实个人感觉还行,就是什么都不能设置就挺蛋疼的,字体大小.背景什么的) 以下是网上找到的教程,在此总结一下: 下载地址:https://gith ...

  7. ChartControl设置坐标轴范围

    需求:有一条坐标轴是用来表示百分比的,所以刻度范围只能是从0 - 100: 解决方法:运行chatcontrol设计器,选中需要设置的轴,如下图: 设置Whole range 的最大最小值: 默认情况 ...

  8. s:if 判断 s:property

    判断<s:property value="XXX"/> 是否是空字符串 则:<s:if  test=" XXX == ''  ">< ...

  9. JS加密对应的c#解码

      escape不编码字符有69个:*,+,-,.,/,@,_,0-9,a-z,A-Z encodeURI不编码字符有82个:!,#,$,&,',(,),*,+,,,-,.,/,:,;,=,? ...

  10. JSP的执行原理

    在一个JSP文件第一次被请求时,JSP引擎把该JSP文件转换成为一个Servlet.而这个引擎本身也是一个Servlet.JSP的运行过程如下所示: (1)JSP引擎先把该JSP文件转换成一个Java ...