前言
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。
- 代理。
- 自动扫描。
流程图如下: