本文是笔者阅读Spring源码的记录文章,由于本人技术水平有限,在文章中难免出现错误,如有发现,感谢各位指正。在阅读过程中也创建了一些衍生文章,衍生文章的意义是因为自己在看源码的过程中,部分知识点并不了解或者对某些知识点产生了兴趣,所以为了更好的阅读源码,所以开设了衍生篇的文章来更好的对这些知识点进行进一步的学习。
本文并非是讲解 Mybatis 源码!!!!而是讲解在SpringBoot 中如何实现MyBatis 的自动装配过程。
由于 Mybatis 并不想其他整合如 AspectJ
有一个启动注解 @EnableAspectJAutoProxy
供我们去作为突破点去分析。那么我们之能看看他的源码。
从pom 文件中我们引入了
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
我们可以推测自动化配置的内容肯定在于此。
我们在源码中找到了一个类 MybatisAutoConfiguration
。看名字就知道了 这个类必然是 Mybatis 的自动化配置的入口。
从 spring.factories
文件中也能知道,MybatisAutoConfiguration
是自动化配置
我们首先来看 MybatisAutoConfiguration
类的结构,以下代码经过精简。
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean{
@Override
public void afterPropertiesSet() {
checkConfigFileExists();
}
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
...
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
...
}
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
...
}
@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug(
"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
}
从上面的内容我们可以了解到 MybatisAutoConfiguration
得知以下几件事:
MybatisAutoConfiguration
作为配置类,依赖于SqlSessionFactory
、SqlSessionFactoryBean
、DataSource
、DataSourceAutoConfiguration
、MybatisLanguageDriverAutoConfiguration
等类。同时引入了MybatisProperties
作为 MyBatis 相关配置类。同时实现了InitializingBean 接口,在 创建的时候回调用 afterPropertiesSet
方法。SqlSessionFactory
, 则 MybatisAutoConfiguration
进行注入。这里注入的是通过SqlSessionFactoryBean.getObject
获取的,后面会解释。SqlSessionTemplate
, 则 MybatisAutoConfiguration
进行注入MapperFactoryBean
、MapperScannerConfigurer
, 则 MybatisAutoConfiguration
进行注入,并引入 AutoConfiguredMapperScannerRegistrar
类。我们主要关注下面几个点。
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
MybatisAutoConfiguration
的声明上面有一大堆注解。
@Configuration
: 声明该类为配置类,不用多说@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
: 表示在容器中注入了 SqlSessionFactory、SqlSessionFactoryBean 后才会注入该bean。这里 SqlSessionFactory 类是MyBatis 的的功能的基础, 而SqlSessionFactoryBean 则是实现了 FactoryBean 接口,也是为了适合Spring 获取 SqlSessionFactory。@ConditionalOnSingleCandidate(DataSource.class)
: 表示 容器中有单例的 DataSource 时才注入容器@EnableConfigurationProperties(MybatisProperties.class)
: 启用配置属性。MybatisProperties 中保存了Mybatis 的相关配置信息@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
: 作用是控制配置类的加载顺序,在DataSourceAutoConfiguration、MybatisLanguageDriverAutoConfiguration加载后加载本类。Mybatis 的配置可以通过 xml的配置类方式进行,但是在Springboot中,我们也可以通过 yml 配置文件直接进行配置(这里说的yml只是SpringBoot配置文件的一种,并不代表只能用yml。下同,yml代表SpringBoot的配置文件配置),如下的两种配置就等价。
在Springboot中的yml配置方式自然不是 Mybatis 提供的,这就需要Spring自己实现了。所以Spring 实现了
SqlSessionFactoryBean用来封装
SqlSessionFactory来完成这个工作。
我们先来看看 MybatisAutoConfiguration
中对于 sqlSessionFactory
的注入。这里可以看到sqlSessionFactory
的注入是通过 SqlSessionFactoryBean.getObject
实现的。 SqlSessionFactoryBean 将各种属性保存后通过getObject 返回了封装好的 SqlSessionFactory 。
注: Mybatis 的配置有两种,一种通过 xml配置,这种需要制定xml的路径,即 ConfigLocation。
另一种像上面的yml配置,这种需要保存其配置的属性,即 ConfigurationProperties
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
// 如果配置了 配置文件地址 config-location 保存起来
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
// 如果配置了配置属性,保存属性
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
// 下面同样逻辑,保存各种配置。
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
// 获取 SqlSessionFactory
return factory.getObject();
}
所以这里来看看 SqlSessionFactoryBean.getObject 是如何初始化 SqlSessionFactory 的:
可以看到,getObject 时会判断 sqlSessionFactory 是否初始化,没有则会对 sqlSessionFactory 进行初始化
@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 {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
// 如果使用了yml 配置。则使用 yml配置
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) {
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);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
// 下面进行一大堆属性的赋值,不再赘述
if (hasLength(this.typeAliasesPackage)) {
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);
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 {
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));
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.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
上面可以看出 :
上面一步适配了 Springboot的配置方式配置。
但是 MyBatis 本身提供了一个@Mapper
注解,被这个注解标注的 接口会被注入到Spring容器中,并与Mapper文件一一映射,但是Spring本身并不支持 @Mapper
注解的扫描。那么@Mapper的工作就需要 MyBatis 自己完成。这一步的完成就在 AutoConfiguredMapperScannerRegistrar
类中。
AutoConfiguredMapperScannerRegistrar
是在 MapperScannerRegistrarNotFoundConfiguration
是声明中通过 @Import 注解引入的。
@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
我们看 AutoConfiguredMapperScannerRegistrar
的声明,实现了 BeanFactoryAware
(可以获得 BeanFactory),实现了ImportBeanDefinitionRegistrar
接口,我们可以通过这个接口提供的registerBeanDefinitions 方法中完成BeanDefinition 的注册修改等操作。
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
return;
}
logger.debug("Searching for mappers annotated with @Mapper");
// 获取BeanFactory 扫描的路径
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
// 设置需要扫描的注解 @Mapper
builder.addPropertyValue("annotationClass", Mapper.class);
// 设置扫描路径
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Stream.of(beanWrapper.getPropertyDescriptors())
// Need to mybatis-spring 2.0.2+
.filter(x -> x.getName().equals("lazyInitialization")).findAny()
.ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
// 注册扫描出来的Bean 的 BeanDefinition
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
逻辑很简单,Spring不给我扫描@Mapper,我自己扫描一遍@Mapper 注解注入不就行了。
在MyBatis单独使用时,调用数据库接口的方式是:
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
而在Spring中创建的方式却是:
UserMapper mapper = (UserMapper)context.getBean("userMapper");
UserMapper mapper = context.getBean(UserMapper.class);
Spring 在获取 UserMapper的bean时候其方式和MyBatis 原生方式并不相同,为了完成和单独使用MyBatis 完成了一样的功能。那么Spring必然是在MyBatis 原生基础上再封装了一层。而这一层的封装就在于MapperFactoryBean 。
我们来看 MapperFactoryBean
的声明。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
SqlSessionDaoSupport
类,这个稍后在看run.getBean("demoFactoryBean");
时,Spring通过反射会发现 DemoFactoryBean
实现了FactoryBean
接口,则会直接调用 其getObject()
方法,并将方法的返回值注入到Spring容器中。而如果想要获得DemoFactoryBean
实例,则需要在 beanName
前加上 &
,即 run.getBean("&demoFactoryBean");
**afterPropertiesSet
方法。 @Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
我们来看一看 MapperFactoryBean
中的 checkDaoConfig
实现
@Override
protected void checkDaoConfig() {
// 调用了 SqlSessionDaoSupport 的checkDaoConfig。判断了一下 sqlSessionTemplate 是否为空
super.checkDaoConfig();
// 这里的mapperInterface 是 Mapper 接口Class
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
// 映射文件存在性验证。
Configuration configuration = getSqlSession().getConfiguration();
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();
}
}
}
这里解释一下关于映射文件存在性的验证,在MyBatis 实现过程中并没有手动调用 configuration.addMapper
方法,而是在映射文件读取过程中一旦解析到
,便会自动进行类型映射的注册。
在上面的函数中, configuration.addMapper(this.mapperInterface);
其实就是将 UserMapper 注册到映射类型中,如果这个接口存在对应的映射文件,那么这一步其实没有什么意义,但是由于这些配置是由我们自行决定配置,无法保证这里的配置的接口一定存在对应的映射文件,所以这里的验证非常有必要。在执行此代码的时候,MyBatis 会检查嵌入的映射接口是否存在对应的映射文件,如果没有则抛出异常。Spring 正是在用这种方式来完成接口对应的映射文件存在性验证。
getObject 的实现很简单,就是对 原生MyBatis 封装了一层。
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
也就是说,对于每一个Mapper, 注入到Spring容器中的都是对应的MapperFactoryBean。在获取Mapper的时候会获取到对应的 MapperFactorybean随后调用其getObject ,将其返回值作为bean返回。
在上面的 AutoConfiguredMapperScannerRegistrar
的声明中,我们看到MapperScannerRegistrarNotFoundConfiguration
需要在 MapperFactoryBean
、 MapperScannerConfigurer
不存在时注入,在 AutoConfiguredMapperScannerRegistrar
中对 MapperScannerConfigurer
进行了 注入,完成了 @Mapper
注解的扫描。下面我们就来看看 MapperScannerConfigurer
的实现。
需要注意的是,@MapperScan
注解核心功能也是基于此类。
可以看到,MapperScannerConfigurer
实现了BeanDefinitionRegistryPostProcessor
, InitializingBean
, ApplicationContextAware
, BeanNameAware
四个接口。这里我们主要看 BeanDefinitionRegistryPostProcessor
的实现方法。
BeanDefinitionRegistryPostProcessor
是 BeanFactory
的后处理器,本文不再解释其用法,具体请看:Spring源码分析七:BeanFactoryPostProcessor 的处理 - invokeBeanFactoryPostProcessors
这里我们直接看 postProcessBeanDefinitionRegistry
方法。
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 为扫描设置各种属性
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));
}
// 注册过滤器
scanner.registerFilters();
// 开始扫描
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
由于 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
的调用时机要早于 BeanFactoryPostProcessor# postProcessBeanFactory
。这就会导致 在执行MapperScannerConfigurer#postProcessBeanDefinitionRegistry
方法时, PropertyResourceConfigurer#postProcessBeanFactory 方法还未执行,属性文件则没有被加载,导致所有对于属性文件的引用将会失效。所以这里为了避免这种情况的发生,选择使用了processPropertyPlaceHolders
方法来手动找出定义的 PropertyResourceConfigurer
并进行提前调用以保证对于属性的引用可以正常工作。
比如:
demo.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.xsd">
<bean id="demo" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<list>
<value>mybatis/demo.properties</value>
</list>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="${basePackage}}"/>
</bean>
</beans>
在 mybatis/demo.properties 文件如下:
basePackage=com.kingfish.springjdbcdemo
但是实际上 ${basePackage}
并没有起到作用,因为在解析 ${basePackage}
时 PropertySourcesPlaceholderConfigurer
还没有被调用,并没有解析属性文件中的内容,所以Spring还不能直接使用。
这一步的作用就是为了解决这种问题。
public void registerFilters() {
boolean acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
// 处理 annotationClass 属性
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// override AssignableTypeFilter to ignore matches on the actual marker interface
// 处理 markerInterface 属性
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
// 修改 acceptAllInterfaces属性
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
// package-info.java 的处理
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
该方法主要是对 annotationClass
和 markerInterface
属性的处理 , 表明扫描过程中只接受标记有注解为 annotationClass
的接口 和 实现了markerInterface
的接口。 如果 annotationClass 和 arkerInterface 有任意一个存在属性,则会将 acceptAllInterfaces
值改变,否则Spring会增加一个默认的过滤器实现 TypeFilter 接口的局部类,接受所有的接口文件。同时对于 package-info.java 命名的java文件,默认不作为逻辑实现接口,将其排除掉。
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);
}
我们这里还是看到 doScan 方法,doScan 的实现类在 ClassPathMapperScanner中,如下。
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用 ClassPathBeanDefinitionScanner#doScan 方法
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 {
// 对 BeanDefinition 进行进一步处理
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
我们这里不再关注 super.doScan 。主要来看看将BeanDefinition 扫描出来后又做了什么,我们来看 processBeanDefinitions 方法的实现。
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
}
}
这里可以看到,将原先的Mapper
的 BeanDefinition
全部动态替换成了 MapperFactoryBean
的BeanDefinition
。比如原先扫描出来的BeanDefinition
是 UserMapper
,这里会将其替换成 MapperFactorybean
。由于在 ClassPathBeanDefinitionScanner#doScan
中扫描时就已经注册到BeanFactory 中,这里获取的是BeanDefinition
的引用,即是将容器中的每个Mapper的 BeanDefinition
改为了MapperFactoryBean
的 BeanDefinition
。
简单一句话来说,就是在Spring容器中为每个Mapper文件动态注册了MapperFactoryBean
。后面获取的Mapper都是进过MapperFactoryBean.getObject
中获取。
以上:内容部分参考
《Spring源码深度解析》
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正