Spring源码系列:MyBatis整合和原理

30 篇文章 3 订阅

前言

Mybatis是啥?Mybatis是一个支持普通SQL查询、存储过程以及映射的一个持久层半ORM框架。那么在了解Spring整合Mybatis这部分源码之前,我们先来看下Mybatis的实际运用。

一. Mybatis的使用

首先,项目的结构如下:
在这里插入图片描述

pom依赖:

 <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.5</version>
</dependency>
<dependency>
   <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.3</version>
</dependency>

User类:

package com.mybatis;

public class User {
    private int id;
    private String name;
    private int age;

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

UserMapper接口:

package com.mybatis.mapper;

import com.mybatis.User;

public interface UserMapper {
    void insertUser(User user);

    User getUser(Integer id);
}

UserMapper.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--关联UserMapper接口 ,下面的id要和接口中对应的方法名称一致,实现一对一的对应-->
<mapper namespace="com.mybatis.mapper.UserMapper">
    <insert id="insertUser" parameterType="user">
        insert into user(id,name,age) values (#{id},#{name},#{age})
    </insert>
    <select id="getUser" parameterType="int" resultType="user">
        select * from user where id=#{id}
    </select>
</mapper>

user.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
   	http://www.springframework.org/schema/beans
   	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--定义别名,这样的话,UserMapper.xml文件当中的返回类型就可以写user,
        否则需要些写全名称:com.pro.domain.User-->
        <property name="typeAliasesPackage" value="com.mybatis"/>
        <!--Mybatis相关的mapper文件位置-->
        <property name="mapperLocations" value="mapper/UserMapper.xml"/>
    </bean>

    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.mybatis.mapper.UserMapper"/>
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
</beans>

Test类:

package com.mybatis;

import com.mybatis.mapper.UserMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.openjdk.jol.vm.VM;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;


public class Test {
    @org.junit.Test
    public void testInsert() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("mapper/user.xml");
        SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) context.getBean("sqlSessionFactory");
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            UserMapper userMapper = (UserMapper) context.getBean("userMapper");
            userMapper.insertUser(new User(2, "ljj", 22));
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }

    @org.junit.Test
    public void testSelect(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("mapper/user.xml");
        SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) context.getBean("sqlSessionFactory");
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            UserMapper userMapper = (UserMapper) context.getBean("userMapper");
            User user = userMapper.getUser(1);
            System.out.println(user);
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
}

结果运行如下。插入:
在这里插入图片描述
数据库:
在这里插入图片描述
查询结果:
在这里插入图片描述

二. 源码分析

从上述案例我们可以发现,在user.xml配置文件中有这么一段关键的配置:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<!--定义别名,这样的话,UserMapper.xml文件当中的返回类型就可以写user,
       否则需要些写全名称:com.pro.domain.User-->
	<property name="typeAliasesPackage" value="com.mybatis"/>
	<!--Mybatis相关的mapper文件位置-->
	<property name="mapperLocations" value="mapper/UserMapper.xml"/>
</bean>

而这里的SqlSessionFactoryBean和Mybatis的使用息息相关(从我们的Test类中可以观察),因此我们可以从SqlSessionFactoryBean开始进行分析。

首先,我们来看下这个类的类关系图:
在这里插入图片描述
首先SqlSessionFactoryBean实现了两个重要的接口:

  • FactoryBean:实现该接口的类,那么通过getBean方法获取到的bean则是对应的getObject()返回的实例。
  • InitializingBean:该接口只有一个方法afterPropertiesSet(),实现这个接口的bean会在初始化的时候调用这个函数。

我们这里就从XML配置上配置的俩bean来作为入口。

2.1 SqlSessionFactoryBean的初始化

SqlSessionFactoryBean作为InitializingBeanFactoryBean接口的一个实现类,具体的初始化逻辑必定在子类当中,我们来看下:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
  ↓↓↓↓↓↓
  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }
  ↓↓↓↓↓↓
  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
       "Property 'configuration' and 'configLocation' can not specified with together");
    // 核心构造
    this.sqlSessionFactory = buildSqlSessionFactory();
  }
  ↓↓↓↓↓↓
  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
	// 声明一个 Configuration 对象,用来装载Mybatis有关的属性
    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
    // 判断当前的SqlSessionFactoryBean是否为通过@Bean注册的
    if (this.configuration != null) {
      targetConfiguration = this.configuration;
      if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      // 创建XML配置构造器对象,用于解析XML文件
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(
          () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }
	// objectFactory / objectWrapperFactory / vfs非空,那么久调用对应的set方法
    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
	// typeAliasesPackage配置的方式有两种,一种是在专门的mybatis-config.xml文件中配置
	// 一种是Spring整合Mybatis时在SqlSessionFactoryBean装配的时候注入的。
    if (hasLength(this.typeAliasesPackage)) {
      // 扫描 typeAliasesPackage 包路径下的所有的实体类的class类型 然后进行过滤,然后注册到Configuration中
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
          .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }
	// 注册属性别名
    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }
	// 注册插件
    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }
	// 注册自定义的类型处理器
    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
          .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }
	// 注册类处理器对象
    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
	// 注册脚本语言,不一样的语言可能又不一样的SQL写法
    if (!isEmpty(this.scriptingLanguageDrivers)) {
      Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
        targetConfiguration.getLanguageRegistry().register(languageDriver);
        LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
      });
    }
    Optional.ofNullable(this.defaultScriptingLanguageDriver)
        .ifPresent(targetConfiguration::setDefaultScriptingLanguage);
	// 注册数据库相关数据
    if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }
	// 若二级缓存非空,则注册
    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

    if (xmlConfigBuilder != null) {
      try {
        // 解析XML对象
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }
	// 设置环境变量
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));
	// 循环遍历我们的mapper.xml文件
    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }
	// 返回DefaultSqlSessionFactory对象,实际上就是new DefaultSqlSessionFactory(targetConfiguration)而已
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }
}

我们可以发现,构建SqlSessionFactory时,支持着许多属性的配置,例如几种常见的:

  • configLocation:使用的外部的Mybatis的配置文件。
  • mapperLocations:配置dao接口的相关映射文件。
  • typeAliasesPackage:实体类的别名。

总的来说就是:

  • 配置文件配置了相关的属性。
  • 就将相关的属性装载到Configuration对象中。
  • 调用构造函数,创建出DefaultSqlSessionFactory对象并返回。

2.2 MapperFactoryBean的创建

紧接着,我们来看下XML配置文件中配置的另一个重要的bean:MapperFactoryBean。首先,我们来看下Test类中的代码:

UserMapper userMapper = (UserMapper) context.getBean("userMapper");

问题是,我们XML配置当中,userMapper对应的应用类型为MapperFactoryBean。而非UserMapper的一个实现。其实,这段代码等价于:

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

实际上,Mybatis在获取映射的过程中,根据配置信息就已经动态地创建了代理类了。我们来看下MapperFactoryBean类的类关系图:
在这里插入图片描述

同理,和SqlSessionFactoryBean类一样,同样实现了俩接口,我们来根据接口来继续分析:

由于实现了InitializingBean接口,我们来看下其afterPropertiesSet()函数的具体实现,代码定位到DaoSupport这个抽象类中:

public abstract class DaoSupport implements InitializingBean {
	@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// 1.dao相关配置的校验
		checkDaoConfig();
		// 2.初始化工作
		try {
			initDao();
		} catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}
}

我们来看下dao相关配置的校验做了什么工作:

public abstract class DaoSupport implements InitializingBean {
	protected abstract void checkDaoConfig() throws IllegalArgumentException;
}

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  @Override
  protected void checkDaoConfig() {
    // 1.notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
    // 即sqlSessionFactory不能为空
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
	// 2.拿到我们SqlSessionFactoryBean初始化过程中,Mybatis的装配对象
    Configuration configuration = getSqlSession().getConfiguration();
    /**
     * <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
     *         <property name="mapperInterface" value="com.mybatis.mapper.UserMapper"/>
     *         <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
     * </bean>
     */
    // 若还没注册对应的mapper信息,则开始注册,也就是将UserMapper注册到映射类型中
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }
}

问题来了,对于这个代码:if (this.addToConfig && !configuration.hasMapper(this.mapperInterface))为什么要判断?因为Mybatis的相关配置都是我们自己配置的,对于接口和xml之间的映射关系,程序无法确定两者一定同时存在并且存在关联。如果没有,则抛出异常。

紧接着,还记得上文提到的这句话吗?Mybatis在获取映射的过程中,根据配置信息就已经动态地创建了代理类了。 现在开始佐证:

由于MapperFactoryBean实现了FactoryBean接口,因此通过getBean返回的对象,必定是通过getObject()函数返回的实例,我们来看下:

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

因此,再将这两行代码放在一起来看,他俩本质是一致的原因是不是就清楚了呢?

// 这里会调用getObject()方法返回,执行getSqlSession().getMapper(this.mapperInterface);
UserMapper userMapper = (UserMapper) context.getBean("userMapper");
↓↓↓↓等同于↓↓↓↓
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

最后,关于initDao方法,也就是Dao的初始化,这里只是提供了一个模板,也就是模板模式具体的设计交给子类去实现,本文就不在赘述了。

protected void initDao() throws Exception {}

2.3 MapperScannerConfigurer配置

回过头,我们来看下案例中的配置文件:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.mybatis.mapper.UserMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

试想一下,如果我有非常多的映射关系,那么上述配置该怎么办呢?总不可能把这段配置都复制一遍吧。正常的Mybatis开发中,尝尝利用注解@MapperScan或者MapperScanner扫描配置的方式(两者本质一样)进行配置,用下文这段代码替换上述配置:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.mybatis"/>
</bean>

最后程序依旧能够正常运行,只要是在com.mybatis目录下的映射类,都会被扫描注入进来。 也就是不需要我们手动一个个加啦。注意:

  • value可以使用分号或者逗号作为分隔符,用于设置多个包路径。
  • 同时,映射器会在指定的包路径下递归的被搜索到。
  • 被发现的映射器会使用Spring的默认命名策略进行命名。
  • 若使用了@Component或者@Name等,则会使用注解中配置的名称(若有)。

接下来开始分析源码,我们来看下MapperScannerConfigurer的类关系图:
在这里插入图片描述
这里挑出两个比较重要的接口:

  • InitializingBean:(没错,又是它),调用afterPropertiesSet()函数。
  • BeanFactoryPostProcessor:调用postProcessBeanFactory()函数。

首先,看下afterPropertiesSet函数:

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(this.basePackage, "Property 'basePackage' is required");
  }
}

这里仅仅是对于basePackage这个属性做个判断,要求其不能为空。

接着,我们来看下postProcessBeanFactory的实现:

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  // left intentionally blank
}

好家伙,空的!我们从类关系图中可以发现,MapperScannerConfigurer同样是BeanDefinitionRegistryPostProcessor类的实现类:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}

我们来看下MapperScannerConfigurer类对这个接口函数的具体实现:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  // 1.processPropertyPlaceHolders属性处理
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }
  // 2.创建一个扫描器对象,并注入相关的属性
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
  scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  // 3.根据配置属性生成过滤器。
  scanner.registerFilters();
  // 4.进行递归扫描
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

总结下三件事情:

  1. processPropertyPlaceHolders属性处理。
  2. 创建一个扫描器对象,并注入相关的属性。
  3. 根据配置属性生成过滤器
  4. 递归扫描指定包中的映射Mapper

2.3.1 processPropertyPlaceHolders属性的作用

mapper目录下新建一个文件user.properties,用于配置扫描地址。

basePackage=com.mybatis

user.xml文件则配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
   	http://www.springframework.org/schema/beans
   	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--定义别名,这样的话,UserMapper.xml文件当中的返回类型就可以写user,
        否则需要些写全名称:com.pro.domain.User-->
        <property name="typeAliasesPackage" value="com.mybatis"/>
        <property name="mapperLocations" value="mapper/UserMapper.xml"/>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    	<!--这里我们希望读的是user.properties中配置的地址-->
        <property name="basePackage" value="${basePackage}"/>
    </bean>

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>mapper/user.properties</value>
            </list>
        </property>
    </bean>
</beans>

此时程序运行结果如下:
在这里插入图片描述
可见Mybatis并没有正确的引用到我们自定义的配置文件。倘若我在MapperScannerConfigurer的配置中添加属性:

<property name="processPropertyPlaceHolders" value="true"/>

再运行之后,结果如下:
在这里插入图片描述


为什么这个属性添加之后,程序就能正常跑通了?我们来看下processPropertyPlaceHolders();这段代码的具体逻辑:

private void processPropertyPlaceHolders() {
  // 找到所有已经注册的PropertyResourceConfigurer
  Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,
      false, false);

  if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
    BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
        .getBeanDefinition(beanName);

    // 模拟Spring环境,将MapperScannerConfigurer类型的bean注册到环境中,进行后处理调用,
    // 即下面的postProcessBeanFactory函数
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    factory.registerBeanDefinition(beanName, mapperScannerBean);

    for (PropertyResourceConfigurer prc : prcs.values()) {
      prc.postProcessBeanFactory(factory);
    }

    PropertyValues values = mapperScannerBean.getPropertyValues();
	// 进行对应的属性替换
    this.basePackage = updatePropertyValue("basePackage", values);
    this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
    this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
    this.lazyInitialization = updatePropertyValue("lazyInitialization", values);
  }
  this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
  this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
      .map(getEnvironment()::resolvePlaceholders).orElse(null);
  this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
      .map(getEnvironment()::resolvePlaceholders).orElse(null);
  this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
      .orElse(null);
}

我们可以发现:

  • registerBeanDefinition函数的调用早于postProcessBeanFactory
  • postProcessBeanFactory函数主要是读取配置文件的。
  • 也因此此时配置文件还没有读取进来,因此属性文件的动态引用就会失效。 故会抛异常。

2.3.2 过滤器生成

这里我们重点关注下这个代码:

scanner.registerFilters();

public void registerFilters() {
  boolean acceptAllInterfaces = true;

  // 处理annotationClass属性
  if (this.annotationClass != null) {
    addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
    acceptAllInterfaces = false;
  }

  // 处理markerInterface属性
  if (this.markerInterface != null) {
    addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
      @Override
      protected boolean matchClassName(String className) {
        return false;
      }
    });
    acceptAllInterfaces = false;
  }
  // 是否接收所有类型的接口
  if (acceptAllInterfaces) {
    // default include filter that accepts all classes
    addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
  }

  // 不扫描package-info.java文件
  addExcludeFilter((metadataReader, metadataReaderFactory) -> {
    String className = metadataReader.getClassMetadata().getClassName();
    return className.endsWith("package-info");
  });
}

涉及到的过滤:

  • annotationClass属性:若有配置,那么扫描的时候,只接收注解中annotationClass标记的接口。
  • markerInterface属性:若有配置,那么扫描的时候,只接收实现了markerInterface接口的接口。
  • acceptAllInterfaces属性:上述两个属性都没配置,那么默认全局处理,即都接收。
  • package-info.java属性:对于命名以package-info为结尾的Java文件,默认不作为逻辑实现的接口,将其排除。

生成完对于的过滤器之后,就可以开始扫描了。

2.3.3 Java文件的扫描

这里我们开始关注scan方法:

scanner.`scan`(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
↓↓↓↓↓↓↓
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
	public int scan(String... basePackages) {
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        // 核心的扫描
        this.doScan(basePackages);
        // 若配置了includeAnnotationConfig属性,那么注册对于注解的处理器
        if (this.includeAnnotationConfig) {
        	// 这里主要完成对于注解处理器的简单注册,例如@Autowired等。
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }

        return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
    }
	↓↓↓this.doScan(basePackages);↓↓↓↓
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		// 首先需要扫描的包路径必定不能为空
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			// 开始扫描Java文件。
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				// 解析scope属性
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				// 若是AnnotatedBeanDefinition类型的bean,需要检测相关属性:Lazy。Primary、DependsOn、Role、Description
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				// 检测当前bean是否已经注册
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					// 若当前bean 是用于生成代理类的bean,则进一步处理。
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
}

上述代码的逻辑比较清晰:

  1. 递归扫描findCandidateComponents
  2. 解析scope属性,校验相关属性(若是AnnotatedBeanDefinition类型的bean)。
  3. 校验bean是否被注册,若没注册则注册。若是生成代理类的bean,则进一步处理。

我们来看下findCandidateComponents方法,其实诸如@Component@Service等注解也是通过这样的方式来扫描的。

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
	if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
		return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
	}
	else {
		return scanCandidateComponents(basePackage);
	}
}
↓↓↓↓scanCandidateComponents↓↓↓↓
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
	Set<BeanDefinition> candidates = new LinkedHashSet<>();
	try {
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
				resolveBasePackage(basePackage) + '/' + this.resourcePattern;
		Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
		// ..省略
		for (Resource resource : resources) {
			// ..省略
			if (resource.isReadable()) {
				try {
					MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
					if (isCandidateComponent(metadataReader)) {
						ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
						sbd.setSource(resource);
						if (isCandidateComponent(sbd)) {
							// ..省略
							candidates.add(sbd);
						}
						// ..省略
	return candidates;
}
↓↓↓↓isCandidateComponent↓↓↓↓
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
	// 若需要排除,则直接返回false
	for (TypeFilter tf : this.excludeFilters) {
		if (tf.match(metadataReader, getMetadataReaderFactory())) {
			return false;
		}
	}
	// includeFilters由registerDefaultFilters()设置初始值
	for (TypeFilter tf : this.includeFilters) {
		if (tf.match(metadataReader, getMetadataReaderFactory())) {
			return isConditionMatch(metadataReader);
		}
	}
	return false;
}
↓↓↓↓相关注解的扫描注册:↓↓↓↓
protected void registerDefaultFilters() {
	this.includeFilters.add(new AnnotationTypeFilter(Component.class));
	// ..省略
}

总结下,无非就是:

  1. package转化为ClassLoader类资源搜索路径所需的packageSearchPath。例如:com.pro转化为classpath*:com/pro/**/*.class
  2. 加载搜索路径下的资源。
  3. 判断是否具备加载的条件。
  4. 添加到返回的集合中。

到这里,Mybatis相关的自动扫描流程也就结束了。

三. 总结(带流程图)

对于上面3个类的源码总结,咱们先说这三个类干啥的哈:

  • SqlSessionFactoryBean:核心,必须配置。配置了它,你就可以使用Mybatis建立代码合数据库之间的关系啦。
  • MapperFactoryBean:必须配置,用于指定对应的Mapper映射接口。同时你可以通过getBean()的方式,直接拿到对于的接口进行数据操作。否则还要通过SqlSessionFactoryBean对象获得。

参考:

UserMapper userMapper = (UserMapper) context.getBean("userMapper");
↓↓↓↓等同于↓↓↓↓
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  • MapperScannerConfigurer:配置了它,你就不用手动加映射类了,自动扫描。

其实也就分别对于三个功能:

  • 核心API。
  • 代理。
  • 自动扫描。

流程图如下:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值