Mybatis-Spring源码分析图解

作者:Jame! 时间:2023-07-18 13:35:07 

Mybatis-Spring

当我们使用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容器中,点进这个类看看

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

Mybatis-Spring源码分析图解

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

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

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

Mybatis-Spring源码分析图解

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

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

来看例子

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<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方法

来源:https://www.cnblogs.com/sunankang/p/15562425.html

标签:Mybatis,Spring,源码
0
投稿

猜你喜欢

  • 浅析Android 的 MediaPlayer类

    2021-06-01 01:50:08
  • SpringBoot应用War包形式部署到外部Tomcat的方法

    2021-10-06 04:59:48
  • Spring Bean实例的创建及构造器的挑选

    2021-08-02 09:35:57
  • @RequestBody的使用详解

    2023-03-08 04:17:28
  • Android NDK开发(C语言-文件读写)

    2022-07-04 17:02:09
  • Python爬虫之爬取2020女团选秀数据

    2023-02-08 13:39:19
  • 基于私钥加密公钥解密的RSA算法C#实现方法

    2022-12-01 07:52:37
  • Java数据机构中关于并查集的详解

    2022-09-05 13:23:53
  • StreamAPI多次消费一个stream代码实例

    2023-10-15 19:44:09
  • C#图像亮度调式与伪彩色图的处理教程(推荐)

    2022-03-26 19:43:54
  • springboot整合EHCache的实践方案

    2023-08-23 23:48:31
  • 自己动手写一个java版简单云相册

    2022-09-08 01:28:56
  • IDEA2020如何打开Run Dashboard的方法步骤

    2023-02-10 15:29:10
  • C/C++ Qt 基本文件读写的基本使用(2种实现)

    2021-09-17 05:15:36
  • Android开发中如何去掉app标题栏的实现

    2023-07-25 00:38:27
  • java实现静默加载Class示例代码

    2023-12-18 22:06:52
  • 微信第三方登录Android实现代码

    2023-07-27 08:05:49
  • C#中DataTable 转换为 Json的方法汇总(三种方法)

    2021-12-12 16:41:00
  • Spring MVC项目中log4J和AOP使用详解

    2022-11-16 08:36:29
  • C#使用dynamic类型访问JObject对象

    2022-03-01 13:05:28
  • asp之家 软件编程 m.aspxhome.com