带着下面的问题进行学习:

  (1)Mybatis 框架或 Spring Framework 框架对数据层 Mapper 接口做了代理,那是做了 JDK 动态代理还是 CGLIB 代理?

  (2)Mapper 接口使用和不使用 @Mapper 注解有什么区别?

  (3)Spring Framework 框架引入 Mybatis 的 jar 包后,Spring Framework 是怎么管理的?

  (4)@MapperScan注解的作用是什么?

  在探究上面的问题前,先了解什么是 FactoryBean,FactoryBean 和 BeanFactory有什么区别?

  BeanFactory 是 Spring Framework 中的一个 Bean 工厂(AnnotationConfigApplicationContext、ClassPathXmlApplicationContext等),可以产生类,它有一个 getBean 方法可以获取到类;

  FactoryBean 是一个 Bean,受 Spring Framework 管理的一个对象,定义 Bean 有好几种方式,比如 xml 的 <bean> 标签,注解 @Bean、@Service 等;

  下面是案例:实现 FactoryBean 接口,重写方法

public class TempBean {
public void query(){
System.out.println("TempBean");
}
}
@Configuration("MyFactroyBean")
public class MyFactroyBean implements FactoryBean {
public void test() {
System.out.println("MyFactroyBean MyFactroyBeanTest");
} @Override
public Object getObject() throws Exception {
return new TempBean();
} @Override
public Class<?> getObjectType() {
return TempBean.class;
} @Override
public boolean isSingleton() {
return true;
}
}
    public static void main(String[] args) {
AnnotationConfigApplicationContext context= new AnnotationConfigApplicationContext(MyFactroyBean.class);
MyFactroyBean myFactroyBean = (MyFactroyBean) context.getBean("&MyFactroyBean");
myFactroyBean.test();
TempBean tempBean = (TempBean) context.getBean("MyFactroyBean");
tempBean.query();
}
//=========结果======

MyFactroyBean MyFactroyBeanTest
   TempBean

  如果你的类实现了 FactoryBean,那么 Spring 存在两个对象:

    一个是 getObject() 返回的对象:当前类指定的名字;
    一个是当前类:"&"+当前类指定的名字;

  当实现了 FactoryBean 后,Spring 在容器实例化过程中,会对项目中的自定义类进行实例化,当前类会被代理成一个 CGLIB 类(使用了@Configuration注解),当你获取当前类时,传递“&xx”,Spring 会进行判断,如果获取是当前类,返回代理类对象,如果是获取 getObject 返回的对象,会直接调用 getObject 方法中的 new xxx();

  下面是Spring容器初始化时,在执行一些后置处理器(ConfigurationClassPostProcessor是对项目的 ComponentScan 注解的路径、@Configuration 注解的类进行扫描解析,超级重要的一个类)过程中,对实现了 FactoryBean 接口的子类进行代理:

  下面是对实现了 FactoryBean 接口的子类的获取:先判断是不是 FactoryBean 类型,如果是加前缀“&"再进行获取;因为前面已经对子类进行了代理,并且存入了 beanDefinitionMap

  但即使上面加了前缀”&“,在后面还是会剔除掉,那怎么判断是获取当前类还是获取getObject方法中的类呢?在 Spring 容器过程中,只会对当前类(实现了 FactoryBean 的子类)进行实例化,当获取时,getSingleton 直接从缓存中获取(每个对象实例化后会放入缓存中 singletonObjects)共享对象,然后 getObjectForBeanInstance 获取实例对象返回,在获取实例对象过程中,会判断是不是 FactoryBean 类型,如果是直接将代理对象返回;

  下面判断是否是一个FactoryBean对象:

    public static boolean isFactoryDereference(@Nullable String name) {
return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));//FACTORY_BEAN_PREFIX=”&"
}

  如果是获取 getObject() 返回的对象,跟上面一样,但在判断是不是一个 FactoryBean 对象时,由于name没有“&”的前缀,所以走下面的流程,不会直接 return beanInstance

  FactroyBean是一个 Bean(可以当作一个业务类,比如连接数据库做一些业务操作),当一个类的依赖关系很复杂时,只需要提供一个简单的接口给外部使用时(将内部的一些关系进行封装维护好)可以使用 FactroyBean 来实现,比如 mybatis 的 SqlSessionFactoryBean 是一个 FactoryBean 实现类,里面的 getObject 方法有一个 afterPropertiesSet 方法,内部将很多依赖(Configuration 配置、TransactionFactory 事务工厂等)进行了处理封装维护到了 SqlSessionFactoryBean,外部只需要传入一个 DataSource 数据源即可,添加 MapperScan 扫描 xml 文件会自动把 xml 的信息维护到 SqlSessionFactoryBean中;

  那么 SqlSessionFactoryBean 什么时候被调用呢?可以看下idea调试的方法调用栈:

  

  从上面几张图片可以看出,当IOC容器Context进行类(Service)初始化时,发现有属性变量(Mapper),会对属性变量(Mapper)进行创建获取自动注入进去,其中会获取到Mapper封装成的一个MapperFactoryBean,执行到 SqlSessionFactoryBean的 afterPropertiesSet 方法对Mapper接口对应的xml进行解析获取;

  MapperFactoryBean 也是一个 FactoryBean,每个 Mapper 接口都会转换成一个 MapperFactoryBean,所以 doCreateBean() 创建获取时可以通过 mapperInterface(Mapper接口)获取到对应的 MapperFactoryBean,它的值是存储到 Configuration的 MapperRegistry变量中;

  那它是什么时候存储到 Configuration的 MapperRegistry变量中的呢?通过下面的图片可以看出,是在解析完xml后放入的:

  当 Mapper 接口在 MapperFactoryBean#getObject() 时,它对Mapper接口进行了代理:MapperRegistry#getMapper()

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//获得代理对象工厂:里面包含Mapper接口信息
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//代理对象实例化
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

  下面是 MapperProxyFactory#newInstance() 的源码:从中可以看到,Mapper 接口对应的代理对象是使用了 JDK 动态代理产生的;【解决了上文的第一个问题,其实从 Mapper 是接口也可以猜出是使用 JDK 动态实现的】

  public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

  其中 MapperProxy 是一个 InvocationHandler 实现类,从前文可以得知调用 Mapper.xxx方法 会进入 MapperProxy#invoke(),最后会执行 mapperMethod.execute(sqlSession, args) 进行SQL查询返回结果;

  综上所述:每个Mapper接口转化成一个MapperFactoryBean,当调用Mapper接口方法执行JDBC操作时,Mapper 接口通过 MapperFactoryBean#getObject() 对Mapper接口进行了 JDK 动态代理。

  对于第三个问题“Spring Framework 框架引入 Mybatis 的 jar 包后,Spring Framework 是怎么管理的?”

  从前文得出每个 Mapper 接口都会转换成一个 MapperFactoryBean,而 MapperFactoryBean 继承了 SqlSessionDaoSupport(mybatis-spring-xx.jar),SqlSessionDaoSupport 继承了 DaoSupport(spring-tx.jar),DaoSupport 实现了 InitializingBean接口,所以Spring容器初始化会执行DaoSupport#afterPropertiesSet方法,会执行里面的checkDaoConfig方法,MapperFactoryBean 重写了checkDaoConfig方法,所以最后会执行MapperFactoryBean#checkDaoConfig方法:

public abstract class DaoSupport implements InitializingBean {
protected final Log logger = LogFactory.getLog(this.getClass()); public DaoSupport() {
} public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
this.checkDaoConfig(); try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
} protected abstract void checkDaoConfig() throws IllegalArgumentException; protected void initDao() throws Exception {
}
}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  @Override
protected void checkDaoConfig() {
super.checkDaoConfig(); notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = getSqlSession().getConfiguration();//得到配置信息
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);//对接口进行xml解析
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
}
Configuration#addMapper
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
} MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//为每个接口配置一个MapperProxyFactory,供后续Mapper接口进行JDK代理
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
//解析类
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();//XML解析
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

  @MapperScan 注解是导入了一个 MapperScannerRegistrar(是实现了 ImportBeanDefinitionRegistrar 接口的,作用是动态往 BeanDefinitionMap 添加 BeanDefinition),的作用是对包路径的 Mapper 接口和对应的XML配置信息进行扫描解析,将所有 Mapper 接口的class信息扫描成对应的 BeanDefition, MapperFactoryBean 类型的 BeanDefition,同时为这个BeanDefition提供一个有参构造方法,参数是 class,在后面Mapper接口进行实例化的 JDK 代理时可以根据这个 class 返回对应的代理对象;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
} Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
} Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
} Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
} Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
} scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));//包扫描解析
}
}

  下面是扫描解析包路径的主要逻辑:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  /**
* Calls the parent search that will search and register all the candidates.
* Then the registered objects are post processed to set them as
* MapperFactoryBeans
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//将每个Mapper解析成BeanDefinitionHolder存放到set集合中
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
//对BeanDefinitionHolder集合进行处理
processBeanDefinitions(beanDefinitions);
} return beanDefinitions;
} private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
} // the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
//给每个BeanDefition提供一个有参构造方法,在后面Mapper接口进行实例化的JDK代理时可以根据这个class返回对应的代理对象;
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//Mapper接口转换成MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
} if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
} if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
}

  综上所述:在 Spring 容器初始化过程中,在对Mapper接口进行实例化的过程中,@MapperScan 注解主要是解析包路径将 Mapper 接口解析成 MapperFactoryBean 的 BeanDefition,这是 Mapper 接口在实例化之前做的事情,在实例化中和之后的过程中,主要利用 Spring的InitializingBean 接口的特性(每个实现了 InitializingBean 接口的子类有一个 afterPropertiesSet 方法,在实例化过程中会执行)来实现对 Mapper 接口信息的初始化,比如 sql 语句的初始化,将这些信息缓存起来放到一个 Map 中:

  上面的是 Mybatis 和 Spring Framework 结合后的初始化流程,那么单独的 Mybatis 初始化流程是怎样的呢?通过下面的案例(前文有详细案例)进行探究:

        String resource = "mybatis-config2.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
mapper.list();
mapper.list();

  通过 debug 出来的方法调用栈可以看出:通过 SqlSessionFactoryBuilder 对 Mapper 接口和 XML 配置进行解析的信息也是存储到 Configuration 的 mappedStatements 中;

  sqlSession.getMapper() 直接从 DefaultSqlSession 中获取,还是跟上面一样在解析完存放到 Configuration 的 MapperRegistry 变量中,代理还是走 JDK 动态代理:

  综上所述: Mybatis 和 Spring Framework 结合后对 Mapper 接口的初始化过程中会转换成 MapperFactoryBean 类型,然后利用 Spring的InitializingBean 接口的特性来实现对 Mapper 接口信息的初始化,再将这些信息缓存起来放到 Configuration 的 mappedStatements 中;而Mybatis直接解析完放到 Configuration 的 mappedStatements 中;

  最后剩余的一个问题:Mapper 接口使用和不使用 @Mapper 注解有什么区别?

  通过查看@Mapper注解的注释,发现这个注解只是一个标识功能,使用与不使用没什么区别,不会影响系统的功能;

Mybatis的初始化和结合Spring Framework后初始化的源码探究的更多相关文章

  1. Mybatis一级缓存和结合Spring Framework后失效的源码探究

    1.在下面的案例中,执行两次查询控制台只会输出一次 SQL 查询: mybatis-config.xml <?xml version="1.0" encoding=" ...

  2. Spring Framework自动装配setAutowireMode和Mybatis案例的源码探究

    由前文可得知, Spring Framework的自动装配有两种方式:xml配置和注解配置: 自动装配的类型有: (1)xml配置中的byType根据类型查找(@Autowired注解是默认根据类型查 ...

  3. 七、Spring之深入理解AOP源码

    Spring之深入理解AOP源码 ​ 在上一篇博文中,我们对AOP有了初步的了解,那么接下来我们就对AOP的实现原理进行深入的分析. ​ 在之前写的那个AOP示例代码当中有这样一个注解:@Enable ...

  4. Mybatis日志源码探究

    一.项目搭建 1.pom.xml <dependencies> <dependency> <groupId>log4j</groupId> <ar ...

  5. Spring Boot 2.0系列文章(五):Spring Boot 2.0 项目源码结构预览

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/04/15/springboot2_code/ 项目结构 结构分析: Spring-boot-pr ...

  6. Spring框架之spring-web http源码完全解析

    Spring框架之spring-web http源码完全解析 Spring-web是Spring webMVC的基础,由http.remoting.web三部分组成. http:封装了http协议中的 ...

  7. Spring框架之spring-web web源码完全解析

    Spring框架之spring-web web源码完全解析 spring-web是Spring webMVC的基础,由http.remoting.web三部分组成,核心为web模块.http模块封装了 ...

  8. 一文读懂Spring动态配置多数据源---源码详细分析

    Spring动态多数据源源码分析及解读 一.为什么要研究Spring动态多数据源 ​ 期初,最开始的原因是:想将答题服务中发送主观题答题数据给批改中间件这块抽象出来, 但这块主要使用的是mq消息的方式 ...

  9. Spring Boot 揭秘与实战 源码分析 - 工作原理剖析

    文章目录 1. EnableAutoConfiguration 帮助我们做了什么 2. 配置参数类 – FreeMarkerProperties 3. 自动配置类 – FreeMarkerAutoCo ...

随机推荐

  1. Linux 三剑客之 awk 实战详解教程

    我们知道 Linux 三剑客,它们分别是:grep.sed.awk.在前边已经讲过 grep 和 sed,没看过的同学可以直接点击阅读,今天要分享的是更为强大的 awk. sed 可以实现非交互式的字 ...

  2. P1049_装箱问题(JAVA语言)

    思路:动态规划的背包问题.使箱子剩余空间最小,也就是使箱内装的物品体积达到最大,我们可将物品的体积视为价值,然后按照01背包问题求解即可. //直接上模板 题目描述 有一个箱子容量为VV(正整数,0 ...

  3. 都在讲Redis主从复制原理,我来讲实践总结

    摘要:本文将演示主从复制如何配置.实现以及实现原理,Redis主从复制三大策略,全量复制.部分复制和立即复制. 本文分享自华为云社区<Redis主从复制实践总结>,原文作者:A梦多啦A . ...

  4. 实验四 Python综合实践

    课程:<Python程序设计> 班级:1843 姓名:章森洋 学号:20184307 实验教师:王志强 实验日期:2020年5月16日 必修/选修: 公选课 1.实验内容 此处填写实验的具 ...

  5. C语言之动态内存管理

    C语言之动态内存管理 大纲: 储存器原理 为什么存在动态内存的开辟 malloc() free() calloc() realloc() 常见错误 例题 柔性数组 零(上).存储器原理 之前我们提到了 ...

  6. 文字变图片——GitHub 热点速览 v.21.14

    作者:HelloGitHub-小鱼干 程序的力量,在 deep-daze 体现得淋漓尽致,你用一句话描述下你的图片需求,它就能帮你生成对应图片.同样的,appsmith 的力量在于你只要拖拽即可得到一 ...

  7. Qt信号槽源码剖析(二)

    大家好,我是IT文艺男,来自一线大厂的一线程序员 上节视频给大家讲解了Qt信号槽的基本概念.元对象编译器.示例代码以及Qt宏:今天接着深入分析,进入Qt信号槽源码剖析系列的第二节视频. Qt信号槽的宏 ...

  8. 快速了解 JavaScript ES2019 的五个新增特性

    ES2019 规范是对 JavaScript 的一个较小的补充,但它仍然带来了一些有用的功能.本文将向你展示五个 ES2019 新增的特性,这些特性或许可以让你的编程轻松一点.这些特性包括 trimS ...

  9. Java 获取Word中的标题大纲(目录)

    概述 Word中的标题可通过"样式"中的选项来快速设置(如图1), 图1 在添加目录时,可将"有效样式"设置为"目录级别"显示(如图2),一 ...

  10. OO 第三单元

    一.JML语言理论基础 JML 是用于对 Java 程序进行规格化设计的一种表示语言,为严格的程序设计提供了一套行之有效的方法. 我个人对于 JML 的几点看法: JML 的规格化设计相较于自然语言的 ...