首页 > 编程语言 >Spring源码系列:MyBatis整合和原理

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

时间:2023-12-08 14:55:25浏览次数:56  
标签:Spring 配置 UserMapper 源码 targetConfiguration MyBatis new null public

前言

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作为InitializingBean和FactoryBean接口的一个实现类,具体的初始化逻辑必定在子类当中,我们来看下:

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));
}

总结下三件事情:
. processPropertyPlaceHolders属性处理。
. 创建一个扫描器对象,并注入相关的属性。
. 根据配置属性生成过滤器
. 递归扫描指定包中的映射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;
	}
}

上述代码的逻辑比较清晰:
. 递归扫描findCandidateComponents。
. 解析scope属性,校验相关属性(若是AnnotatedBeanDefinition类型的bean)。
. 校验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));
	// ..省略
}

总结下,无非就是:
. 将package转化为ClassLoader类资源搜索路径所需的packageSearchPath。例如:com.pro转化为classpath:com/pro/**/.class。
. 加载搜索路径下的资源。
. 判断是否具备加载的条件。
. 添加到返回的集合中。
到这里,Mybatis相关的自动扫描流程也就结束了。

三. 总结(带流程图)

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

  • SqlSessionFactoryBean:核心,必须配置。配置了它,你就可以使用Mybatis建立代码合数据库之间的关系啦。
  • MapperFactoryBean:必须配置,用于指定对应的Mapper映射接口。同时你可以通过getBean()的方式,直接拿到对于的接口进行数据操作。否则还要通过SqlSessionFactoryBean对象获得。
    参考:
UserMapper userMapper = (UserMapper) context.getBean("userMapper");
↓↓↓↓等同于↓↓↓↓
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  • MapperScannerConfigurer:配置了它,你就不用手动加映射类了,自动扫描。
    其实也就分别对于三个功能:
  • 核心API。
  • 代理。
  • 自动扫描。
    流程图如下:

标签:Spring,配置,UserMapper,源码,targetConfiguration,MyBatis,new,null,public
From: https://www.cnblogs.com/kaizenwyy/p/17887173.html

相关文章

  • Mybatis Plus 自定义 TypeHandler
    在MyBatisPlus中,可以自定义TypeHandler来处理特殊的类型转换。下面是如何自定义一个TypeHandler的步骤:我们需要创建一个实现org.apache.ibatis.type.TypeHandler接口的类。这个类需要实现以下几个方法:setParameter(PreparedStatementps,inti,Tparameter,JdbcTypejdbc......
  • ModelAndViewContainer、ModelMap、Model、ModelAndView详细介绍【享学Spring MVC】
    前言写这篇文章非我本意,因为我觉得对如题的这个几个类的了解还是比较基础且简单的一块内容,直到有超过两个同学问过我一些问题的时候:通过聊天发现小伙伴都听说过这几个类,但对于他们的使用、功能定位是傻傻分不清楚的(因为名字上都有很多的相似之处)。那么书写本文就是当作一篇科普类......
  • mini-spring 学习笔记—AOP
    切点表达式ClassFilter和MethodMatcher这两个接口都定义了一个叫做mathes的方法,用于匹配ClassFilter接口规范了类匹配器的行为booleanmatches(Class<?>clazz);MethodMatcher接口规范了方法匹配器的行为booleanmatches(Methodmethod,Class<?>targetClass);Poi......
  • SpringBoot高级开发(9)Spring中的HttpSession
    1、简述HttpSession是javaWeb提供的,用来处理会话事务的。session数据保存在后台,当然首次开启会话(即调用req.getSession())的时候也会将该SessionID数值传给前端用作Cookie2、作用范围首次访问服务器开始,浏览器关闭后就结束。后端的Session可以存储30分钟,如果30分钟无任何请求,就......
  • SpringBoot+线程池实现高频调用http接口并多线程解析json数据
    场景Springboot+FastJson实现解析第三方http接口json数据为实体类(时间格式化转换、字段包含中文):https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/134872936Java中ExecutorService线程池的使用(Runnable和Callable多线程实现):https://blog.csdn.net/BADAO_LIUMAN......
  • Springboot+FastJson实现解析第三方http接口json数据为实体类(时间格式化转换、字段包
    场景若依前后端分离版手把手教你本地搭建环境并运行项目:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662在上面搭建SpringBoot项目的基础上,并且在项目中引入fastjson、hutool、lombok等所需依赖后。系统需要对接第三方http接口获取返回的数据,并将json数......
  • C语言源码的陷波器设计及调试总结
    一前记音频信号处理中,限波器是一个常用的算法。这个算法难度不是很高,可用起来却坑很多。二源码解析1滤波器的核心函数,这里注意两点,一个是带宽不能太宽了,太宽了杀伤力太大了,容易出问题。另外一个就是滤波器的阶数非常重要,假如想滤波宽度尽量窄一些,那就阶数尽量高一些......
  • 记录springboot的一次使用socketio的经历
    pom中加入依赖<dependency><groupId>com.corundumstudio.socketio</groupId><artifactId>netty-socketio</artifactId><version>2.0.6</version></dependency>......
  • MySQL服务器8核32G max_connections设置为10000的情况,springboot里面的Druid参数配置
    MySQL服务器8核32Gmax_connections设置为10000的情况,springboot里面的Druid参数配置多少合适啊,MySQL服务器8核32G,max_connections设置为10000,确实是相当大的一个配置啊。对于Druid的参数配置,得看你系统的具体情况。一般来说,你可以考虑以下几个参数:initialSize:连接池的初始大小,你......
  • mybatis解析settings标签
    settings标签也是一个很重要的标签,虽然我们在使用的时候,没怎么配置settings标签里面的内容。好像一开始为了看sql语句,我们在settings标签里面配置了日志。<settings><settingname="logImpl"value="SLF4J"/></settings>其他的好像就没干什么了。其实settings标签......