Mybatis-Spring源码分析

Mybatis-Spring

博主技术有限,本文难免有错误的地方,如果您发现了欢迎评论私信指出,谢谢
JAVA技术交流群:737698533

当我们使用mybatis和spring整合后为什么下面的代码可以运行?

Mybatis-Spring源码分析

Mybatis-Spring源码分析

一个问题:

我就写了个mapper接口为什么能用?

首先来看,在spring的配置xml中有一段


这段xml的作用是将一个类添加到spring容器中,点进这个类看看

Mybatis-Spring源码分析

它实现了一个 BeanDefinitionRegistryPostProcessor接口,关于这个接口的作用和执行时机上篇博客写过了,这里就不再赘述

那么它必然实现 postProcessBeanDefinitionRegistry方法,点击这个方法查看

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
        processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    ..........

    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

其中将接口注册到spring容器中在最后一行,先来看 ClassPathMapperScanner这个类,它继承了 ClassPathBeanDefinitionScanner这个扫描器

Mybatis-Spring源码分析

scan的具体代码

public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

    doScan(basePackages);

    // Register annotation config processors, if necessary.

    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

这个是spring内部的扫描方法,当它走到doScan的时候,因为ClassPathMapperScanner这个类重写了doScan方法,所以会调用子类重写的方法

@Override
public Set doScan(String... basePackages) {
    Set beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
        LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
                    + "' package. Please check your configuration.");
    } else {
        processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

通过包名获取BeanDefinitionHolder,现在它获取到了User接口的BeanDefinitionHolder,然后判断如果BeanDefinitionHolder的集合为空,也就是没有找到mapper的情况则不做任何处理,而现在有一个UserMapper的,进入else

private void processBeanDefinitions(Set beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (AbstractBeanDefinition) holder.getBeanDefinition();
        .........

        //给这个BeanDefinition设置通用的构造参数
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
        //设置这个BeanDefinition的BeanCalss类型为MapperFactoryBean类型
        definition.setBeanClass(this.mapperFactoryBeanClass);
      .........

        if (!definition.isSingleton()) {
            BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
            if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
                registry.removeBeanDefinition(proxyHolder.getBeanName());
            }
            registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
        }
    }
}

将MapperFactoryBean类设置为了UserMapperBeanDefinition的class

Mybatis-Spring源码分析

spring在创建这个userMapper这个Bean的时候会使用这个有参构造将当前这个UserMapper类型设置到mapperInterface属性上 (为啥使用有参构造而不是无参来初始化对象我也不知道…..这和spring推断构造方法有关,以后学会了在来写)

这个MapperFactoryBean实现了一个 FactoryBean接口,这个接口可以让我们自定义获取bean的操作

回到spring的代码,例如当我们使用context.getBean(xxx.class)的时候

Mybatis-Spring源码分析

spring将xxx.class类型解析为bean名称,通过名称去获取

protected  T doGetBean(
    String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly)
    throws BeansException {
    //获取对应的beanName
    String beanName = transformedBeanName(name);
    Object bean;
    Object sharedInstance = getSingleton(beanName);

    if (sharedInstance != null && args == null) {
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    .......

    // Create bean instance.

    if (mbd.isSingleton()) {
        sharedInstance = getSingleton(beanName, () -> {
            try {
                //真正创建对象的地方
                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 = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
}

首先是调用 getSingleton方法,尝试获取存在缓存中的bean(其实就是三个Map,key为bean名称,value是对象),那现在是首次获取map中没有

然后执行到下面的createBean,当创建完这个bean后spring需要判断这个bean是一个普通bean还是一个FactoryBean,程序员是想要获取普通bean还是FactoryBean,还是FactoryBean的getObject方法返回的从工厂生成的对象

咱们一段一段看

protected Object getObjectForBeanInstance(
    Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

    if (BeanFactoryUtils.isFactoryDereference(name)) {
        if (beanInstance instanceof NullBean) {
            return beanInstance;
        }
        if (!(beanInstance instanceof FactoryBean)) {
            throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
        }
    }
    .....

}

BeanFactoryUtils.isFactoryDereference(name)的作用是一个字符串判断,当返回传入名称是否为工厂,如果name不为空,并且以&开头返回true

这个方法在下面的判断也使用到了,记一下它的作用即可

来看例子

Mybatis-Spring源码分析

Mybatis-Spring源码分析

在我们使用FactoryBean通过context.getBean(“工厂Bean名称”)的时候获取的是FactoryBean的getObject生成的对象,如果我们想获取FactoryBean的引用则需要在名称前面加一个 &符号

Mybatis-Spring源码分析

回来看代码,如果这个bean的引用是一个NullBean类型则直接返回引用,下面有做了一个判断

if (!(beanInstance instanceof FactoryBean))再次判断这个bean是不是一个FactoryBean,如果为true则抛出异常,这个好理解,因为我们在getBean的时候完全可以将一个普通的bean名称前面加上&符号

主要的判断在下面的这个if

if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
    return beanInstance;
}

现在有3中情况

  1. 当前的bean是一个普通的bean 第一个条件false 取反 true 第二个条件false 结果true,直接返回bean实例
  2. 当前是一个FactoryBean,想通过工厂获取Bean 第一个条件 true 取反false 第二个条件false 结果false,进行下面的操作
  3. 当前是一个FactoryBean,想获取工厂的引用 第一个条件 true 取反 false 第二个条件 true 结果 true 直接返回factoryBean实例

当前我们是想通过FactoryBean获取对象,那么不进if,继续下面的代码

Object object = null;
// 如果beanDefinition为null,则尝试从缓存中获取给定的FactoryBean公开的对象
if (mbd == null) {
    //尝试从缓存中加载bean
    object = getCachedObjectForFactoryBean(beanName);
}
// 未能从缓存中获得FactoryBean公开的对象,则说明该bean是一个新创建的bean
if (object == null) {
    FactoryBean factory = (FactoryBean) beanInstance;
    if (mbd == null && containsBeanDefinition(beanName)) {
        mbd = getMergedLocalBeanDefinition(beanName);
    }
    boolean synthetic = (mbd != null && mbd.isSynthetic());
    // 从给定的FactoryBean中获取指定的beanName对象
    object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;

主要来看 getObjectFromFactoryBean

protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) {
    if (factory.isSingleton() && containsSingleton(beanName)) {
        synchronized (getSingletonMutex()) {
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                //调用factoryBean的getObject方法
                object = doGetObjectFromFactoryBean(factory, beanName);
                Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
                if (alreadyThere != null) {
                    object = alreadyThere;
                }
            }
            ..........

        }
    }
}

doGetObjectFromFactoryBean方法

private Object doGetObjectFromFactoryBean(FactoryBean factory, String beanName) throws BeanCreationException {
    Object object;
    try {
        if (System.getSecurityManager() != null) {
            AccessControlContext acc = getAccessControlContext();
            try {
                object = AccessController.doPrivileged((PrivilegedExceptionAction) factory::getObject, acc);
            }
            catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        }
        else {
            //调用重写的getObject方法
            object = factory.getObject();
        }
    }
    .......

    return object;
}

也就是说当我们getBean(“userMapper”)的时候其实是调用FactoryBean的getObject方法,代码回到mybatis-spring项目的MapperFactoryBean类中的getObject方法

@Override
public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}

@Override
public  T getMapper(Class type) {
    return configuration.getMapper(type, this);
}

public  T getMapper(Class type, SqlSession sqlSession) {
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
    }
}

public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

到最后发现是通过jdk的动态代理来生成的对象,那么回答开始的问题

我就写了个接口为什么能用?

因为mybatis在spring加载bean之前修改了beanDefinition,通过MapperScannerConfigurer类实现的BeanDefinitionRegistryPostProcessor接口中将我们定义的一些mapper接口的BeanDefinition的BeanClass属性修改为了MapperFactoryBean,而这个类实现了FactoryBean,我们获取接口实际上是通过FactoryBean的getObject方法

Original: https://www.cnblogs.com/sunankang/p/15562425.html
Author: Jame!
Title: Mybatis-Spring源码分析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/620906/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • MYSQL–>事务

    事务是一组操作的集合,它是一个不可分割的工作单位。 事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,这些操作要么同时成功,要么同时失败 开启事务—->…

    数据库 2023年6月14日
    088
  • Eureka详解系列(四)–Eureka Client部分的源码和配置

    按照原定的计划,我将分三个部分来分析 Eureka 的源码: 今天,我们来研究第二部分的源码。 我的思路是这样子的:先明确 Eureka Client 拥有哪些功能,然后从源码角度…

    数据库 2023年6月6日
    093
  • cron 表达式

    cron 表达式 1.简介:一个cron表达式最少有5个空格来分割时间元素,总共有7个元素,分别如下: ① 秒(0-59) ② 分钟(0-59) ③ 小时(0-23) ④ 天(月的…

    数据库 2023年6月16日
    0105
  • 软件基础的理论(1)

    软件基础的理论 一, 什么是软件产品 它是一个逻辑产品,没有实体,包括程序,文档和数据,需要通过终端设备才能体现出来功能和作用 二, 软件产品的中间过程文档 客户需求 &#…

    数据库 2023年6月16日
    0101
  • Centos安装mysql57

    1.1 MySQL安装 1.1.1 下载 wget 命令 yum -y install wget 1.1.2 在线下载mysql安装包 wget https://dev.mysql…

    数据库 2023年5月24日
    0127
  • 日月既往,不可复追,暑期实习结束!

    在从上海回南京的高铁上码下了这篇文章,心中感慨万千, 两个月弹指一挥间,初来时还略有不适,突然要走了竟然还生出一些留念,所谓 “天可补,海可填,南山可移,日月既往,不可…

    数据库 2023年6月6日
    0161
  • MySQL主从备库过滤参数分析和测试

    测试环境: GTID的主从复制,主库(9900)——》备库(9909),存在测试库表: 9900_db1库:t1、t2、t3、t4、t5表 9900_db2库:t6、t7、t8、t…

    数据库 2023年6月16日
    076
  • 送分题,ArrayList 的扩容机制了解吗?

    1. ArrayList 了解过吗?它是啥?有啥用? 众所周知,Java 集合框架拥有两大接口 Collection 和 Map,其中, Collection 麾下三生子 List…

    数据库 2023年6月6日
    084
  • B树详解

    B树系列文章 1. B树-介绍 2. B树-查找 3. B树-插入 4. B树-删除 什么是B树 B树(英语:B-tree)是一种自平衡的树,能够保持数据有序。使用B树这种数据结构…

    数据库 2023年6月14日
    0108
  • 设计模式之(9)——适配器模式

    定义:适配器模式是将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作,在软件设计中我们需要将一些”现存的对象&#8…

    数据库 2023年6月14日
    051
  • Spring Bean的作用域

    Spring Bean的作用域或者说范围主要有五种: 作用 描述 singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的…

    数据库 2023年6月16日
    064
  • 容器化 | 使用 Alpine 构建 Redis 镜像

    上一期我们介绍了几种常见的构建镜像方式,并给出了功能对比、决策树等作为选型参考。本期我们将演示如何使用 Alpine 构建一个 Redis 镜像。 Alpine 系统使用 apk …

    数据库 2023年5月24日
    0103
  • [SQLServer]NetCore中将SQLServer数据库备份为Sql脚本

    描述: 最近写项目收到了一个需求, 就是将 SQL Server数据库备份为Sql脚本, 如果是My Sql之类的还好说, 但是在网上搜了一大堆, 全是教你怎么操作 SSMS的, …

    数据库 2023年6月9日
    0102
  • mybatis order by concat用法

    由于项目中用到了一个关联查询,关联的表中都有id字段,在排序时,使用${id},获取值时,一直报 Column ‘id’ in order clause i…

    数据库 2023年6月11日
    082
  • Python 字符串的常用方法

    Python 包含6种数据类型,其中Number(数字)、String(字符串)、Tuple(元组)、 List(列表)、Dictionary(字典)、Set(集合); 这节主要讲…

    数据库 2023年6月16日
    081
  • JUC学习笔记(一)

    1、什么是 JUC 1.1、JUC简介 在 Java 中,线程部分是一个重点,本篇文章说的 JUC 也是关于线程的。JUC 就是 java.util .concurrent 工具包…

    数据库 2023年6月6日
    0107
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球