Mybatis-Spring源码分析图解

Mybatis-Spring

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

一个问题:

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

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

<bean id="configurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <property name="basePackage" value="com.jame.dao"/>
</bean>

这段xml的作用是将一个类添加到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这个扫描器

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<BeanDefinitionHolder> doScan(String... basePackages) {
    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 {
        processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

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

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (AbstractBeanDefinition) holder.getBeanDefinition();
       	.........
        //主要看这行
        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

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

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

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

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

protected <T> T doGetBean(
    String name, @Nullable Class<T> 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

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

来看例子

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

回来看代码,如果这个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<Object>) 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> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

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

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

protected T newInstance(MapperProxy<T> 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方法

到此这篇关于Mybatis-Spring源码分析的文章就介绍到这了,更多相关Mybatis-Spring源码分析内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring的BeanFactoryPostProcessor接口示例代码详解

    接口简介 BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 时对外暴露的扩展点,Spring IoC 容器允许 BeanFactoryPostProcessor 在容器实例化任何 bean 之前读取 bean 的定义,并可以修改它. BeanDefinitionRegistryPostProcessor 继承自 BeanFactoryPostProcessor,比 BeanFactoryPostProcessor 具有更高的优先级,主要用来在

  • Spring注解驱动扩展原理BeanFactoryPostProcessor

    1.扩展原理-BeanFactoryPostProcessor BeanFactoryPostProcessor * 扩展原理: * BeanPostProcessor:bean后置处理器,bean创建对象初始化前后进行拦截工作的 * * 1.BeanFactoryPostProcessor:beanFactory的后置处理器: * 在BeanFactory标准初始化之后调用,来定制和修改BeanFactory的内容: * 所有的bean定义已经保存加载到beanFactory,但是bean的实

  • Spring源码BeanFactoryPostProcessor详解

    Spring源码分析-BeanFactoryPostProcessor BeanFactoryPostProcessor接口是Spring提供的对Bean的扩展点,它的子接口是BeanDefinitionRegistryPostProcessor @FunctionalInterface public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory b

  • Spring源码分析容器启动流程

    目录 前言 源码解析 1.初始化流程 流程分析 核心代码剖析 2.刷新流程 流程分析 核心代码剖析 前言 本文基于 Spring 的 5.1.6.RELEASE 版本 Spring的启动流程可以归纳为三个步骤: 1.初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中 2.将配置类的BeanDefinition注册到容器中 3.调用refresh()方法刷新容器 Spring Framework 是 Java 语言中影响最为深远的框架之一,其

  • Mybatis-Spring源码分析图解

    Mybatis-Spring 当我们使用mybatis和spring整合后为什么下面的代码可以运行? 一个问题: 我就写了个mapper接口为什么能用? 首先来看,在spring的配置xml中有一段 <bean id="configurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanNam

  • Mybatis源码分析之存储过程调用和运行流程

    这一篇我们学习一下Mybatis调用存储过程的使用和运行流程.首先我们先创建一个简单的存储过程 DELIMITER $ CREATE PROCEDURE mybatis.ges_user_count(IN age INT, OUT user_count INT) BEGIN SELECT COUNT(*) FROM users WHERE users.age=age INTO user_count; END $ 这个存储过程的含义其实比较简单的,就是输入age,然后执行select count(

  • MyBatis 源码分析 之SqlSession接口和Executor类

    mybatis框架在操作数据的时候,离不开SqlSession接口实例类的作用.可以说SqlSession接口实例是开发过程中打交道最多的一个类.即是DefaultSqlSession类.如果笔者记得没有错的话,早期是没有什么getMapper方法的.增删改查各志有对应的方法进行操作.虽然现在改进了很多,但是也保留了很多.我们依旧可以看到类似于selectList这样子的方法.源码的例子里面就可以找到.如下 SqlSession session = sqlMapper.openSession(T

  • 解析Mybatis判断表达式源码分析

    在我们开发过程中用 Mybatis 经常会用到下面的例子 Mapper如下 Map<String ,String > testArray(@Param("array") String [] array); XMl中的sql如下 <select id="testArray" resultType="map"> select * from t_ams_ac_pmt_dtl where cpt_pro=#{cptProp} &l

  • Mybatis源码分析之插件模块

    Mybatis插件模块 插件这个东西一般用的比较少,就算用的多的插件也算是PageHelper分页插件: PageHelper官网:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md 官网上这个也有谈到Mybatis的插件流程分析. 使用示例 插件类 记录SQL执行的时间, 1.在JDK8之前必须实现Interceptor接口中的三个方法,在JDK8之后只需要实现intercept方法即可: 2.加上

  • spring @Conditional的使用与扩展源码分析

    目录 @Conditional的使用 WindowsCondition LinuxCondition Conditional的扩展 ConditionalOnBean ConditionalOnProperty 源码分析 @Conditional的使用 @Conditional可以根据条件来判断是否注入某些Bean. package com.morris.spring.config; import com.morris.spring.condition.LinuxCondition; impor

  • lazy init控制加载在Spring中如何实现源码分析

    目录 一.lazy-init说明 二.lazy-init 属性被设置的地方 三.lazy-init发挥作用的地方 四.问答 一.lazy-init说明 ApplicationContext实现的默认行为就是在启动时将所有singleton bean提前进行实例化(也就是依赖注入). 提前实例化意味着作为初始化过程的一部分,ApplicationContext实例会创建并配置所有的singleton bean. 通常情况下这是件好事,因为这样在配置中的任何错误就会即刻被发现(否则的话可能要花几个小

  • 使用dynamic-datasource-spring-boot-starter实现多数据源及源码分析

    简介 前两篇博客介绍了用基本的方式做多数据源,可以应对一般的情况,但是遇到一些复杂的情况就需要扩展下功能了,比如:动态增减数据源.数据源分组,纯粹多库 读写分离 一主多从.从其他数据库或者配置中心读取数据源等等.其实就算没有这些需求,使用这个实现多数据源也比之前使用AbstractRoutingDataSource要便捷的多 dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器. github: https://g

随机推荐