47 KiB
精尽 MyBatis 源码解析 —— Spring 集成(二)之初始化
1. 概述
在前面的,我们已经看了四篇 MyBatis 初始化相关的文章:
- 《MyBatis 初始化(一)之加载 mybatis-config》
- 《MyBatis 初始化(二)之加载 Mapper 映射配置文件》
- 《MyBatis 初始化(三)之加载 Statement 配置》
- 《MyBatis 初始化(四)之加载注解配置》
那么,本文我们就来看看,Spring 和 MyBatis 如何集成。主要涉及如下三个包:
annotation
config
mapper
2. SqlSessionFactoryBean
org.mybatis.spring.SqlSessionFactoryBean
,实现 FactoryBean、InitializingBean、ApplicationListener 接口,负责创建 SqlSessionFactory 对象。
使用示例如下:
<!-- simplest possible SqlSessionFactory configuration -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- Directly specify the location of the MyBatis mapper xml file. This
is NOT required when using MapperScannerConfigurer or
MapperFactoryBean; they will load the xml automatically if it is
in the same classpath location as the DAO interface. Rather than
directly referencing the xml files, the 'configLocation' property
could also be used to specify the location of a MyBatis config
file. This config file could, in turn, contain <mapper>
elements that point to the correct mapper xml files.
-->
<property name="mapperLocations" value="classpath:org/mybatis/spring/sample/mapper/*.xml" />
</bean>
另外,如果胖友不熟悉 Spring FactoryBean 的机制。可以看看 《Spring bean 之 FactoryBean》 文章。
2.1 构造方法
// SqlSessionFactoryBean.java
private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);
/**
* 指定 mybatis-config.xml 路径的 Resource 对象
*/
private Resource configLocation;
private Configuration configuration;
/**
* 指定 Mapper 路径的 Resource 数组
*/
private Resource[] mapperLocations;
private DataSource dataSource;
private TransactionFactory transactionFactory;
private Properties configurationProperties;
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
private SqlSessionFactory sqlSessionFactory;
//EnvironmentAware requires spring 3.1
private String environment = SqlSessionFactoryBean.class.getSimpleName();
private boolean failFast;
private Interceptor[] plugins;
private TypeHandler<?>[] typeHandlers;
private String typeHandlersPackage;
private Class<?>[] typeAliases;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
//issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider;
private Class<? extends VFS> vfs;
private Cache cache;
private ObjectFactory objectFactory;
private ObjectWrapperFactory objectWrapperFactory;
// 省略 setting 方法
- 是不是各种熟悉的属性,这里就不多解释每个对象了。你比我懂,嘿嘿。
2.2 afterPropertiesSet
#afterPropertiesSet()
方法,构建 SqlSessionFactory 对象。代码如下:
// SqlSessionFactoryBean.java
@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");
// 创建 SqlSessionFactory 对象
this.sqlSessionFactory = buildSqlSessionFactory();
}
/**
* Build a {@code SqlSessionFactory} instance.
*
* The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
* {@code SqlSessionFactory} instance based on an Reader.
* Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).
*
* @return SqlSessionFactory
* @throws IOException if loading the config file failed
*/
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
// 初始化 configuration 对象,和设置其 `configuration.variables` 属性
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
// 设置 `configuration.objectFactory` 属性
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
// 设置 `configuration.objectWrapperFactory` 属性
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
// 设置 `configuration.vfs` 属性
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
// 设置 `configuration.typeAliasesPackage` 属性
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
}
}
// 设置 `configuration.typeAliases` 属性
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
}
}
// 初始化 `configuration.interceptorChain` 属性,即拦截器
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
}
}
// 扫描 typeHandlersPackage 包,注册 TypeHandler
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
}
}
// 如果 typeHandlers 非空,注册对应的 TypeHandler
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
}
}
// 设置 `configuration.databaseId` 属性
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
// 设置 `configuration.cache` 属性
if (this.cache != null) {
configuration.addCache(this.cache);
}
// <1> 解析 mybatis-config.xml 配置
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();
}
}
// 初始化 TransactionFactory 对象
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
// 设置 `configuration.environment` 属性
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// 扫描 Mapper XML 文件们,并进行解析
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.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 or no matching resources found");
}
// 创建 SqlSessionFactory 对象
return this.sqlSessionFactoryBuilder.build(configuration);
}
- 代码比较长,胖友自己耐心看下注释,我们只挑重点的瞅瞅。
<1>
处,调用XMLConfigBuilder#parse()
方法,解析mybatis-config.xml
配置。即对应 《MyBatis 初始化(一)之加载 mybatis-config》 文章。<2>
处,调用XMLMapperBuilder#parse()
方法,解析 Mapper XML 配置。即对应 《MyBatis 初始化(二)之加载 Mapper 映射配置文件》 。<3>
处,调用SqlSessionFactoryBuilder#build(Configuration config)
方法,创建 SqlSessionFactory 对象。- 另外,我们看到
LOGGER
的使用。那么,可能胖友会跟艿艿有一样的疑问,org.mybatis.logging
包下,又定义了 Logger 呢?因为想使用方法参数为Supplier<String>
的方法,即使用起来更加方便。这也是mybatis-spring
项目的logging
包的用途。感兴趣的胖友,可以自己去看看。
2.3 getObject
#getObject()
方法,获得 SqlSessionFactory 对象。代码如下:
// SqlSessionFactoryBean.java
@Override
public SqlSessionFactory getObject() throws Exception {
// 保证 SqlSessionFactory 对象的初始化
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
2.4 getObjectType
// SqlSessionFactoryBean.java
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
2.5 isSingleton
// SqlSessionFactoryBean.java
@Override
public boolean isSingleton() {
return true;
}
2.6 onApplicationEvent
#onApplicationEvent()
方法,监听 ContextRefreshedEvent 事件,如果 MapperStatement 们,没有都初始化都完成,会抛出 IncompleteElementException 异常。代码如下:
// SqlSessionFactoryBean.java
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
// 如果 MapperStatement 们,没有都初始化完成,会抛出 IncompleteElementException 异常
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
- 使用该功能时,需要设置
fastFast
属性,为true
。
在 mybatis-spring
项目中,提供了多种配置 Mapper 的方式。下面,我们一种一种来看。😈 实际上,配置 SqlSessionFactoryBean.mapperLocations
属性,也是方式之一。嘿嘿嘿。
3. MapperFactoryBean
org.mybatis.spring.mapper.MapperFactoryBean
,实现 FactoryBean 接口,继承 SqlSessionDaoSupport 抽象类,创建 Mapper 对象。
使用示例如下:
<!-- Directly injecting mappers. The required SqlSessionFactory will be autowired. -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" autowire="byType">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
</bean>
- 该示例来自
org.mybatis.spring.sample.SampleMapperTest
单元测试。胖友可以基于它调试。
3.1 MapperFactoryBean
// MapperFactoryBean.java
/**
* Mapper 接口
*/
private Class<T> mapperInterface;
/**
* 是否添加到 {@link Configuration} 中
*/
private boolean addToConfig = true;
public MapperFactoryBean() {
//intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
// 省略 setting 方法
3.2 checkDaoConfig
// MapperFactoryBean.java
@Override
protected void checkDaoConfig() {
// <1> 校验 sqlSessionTemplate 非空
super.checkDaoConfig();
// <2> 校验 mapperInterface 非空
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
// <3> 添加 Mapper 接口到 configuration 中
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();
}
}
}
-
该方法,是在
org.springframework.dao.support.DaoSupport
定义,被#afterPropertiesSet()
方法所调用,代码如下:// DaoSupport.java public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { this.checkDaoConfig(); try { this.initDao(); } catch (Exception var2) { throw new BeanInitializationException("Initialization of DAO failed", var2); } }
- 所以此处,就和 「2.2 afterPropertiesSet」 的等价。
-
<1>
处,调用父SqlSessionDaoSupport#checkDaoConfig()
方法,校验sqlSessionTemplate
非空。😈 关于 SqlSessionDaoSupport 抽象类,后续我们详细解析。 -
<2>
处,校验mapperInterface
非空。 -
<3>
处,调用Configuration#addMapper(mapperInterface)
方法,添加 Mapper 接口到configuration
中。
3.3 getObject
#getObject()
方法,获得 Mapper 对象。注意,返回的是基于 Mapper 接口自动生成的代理对象。代码如下:
// MapperFactoryBean.java
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
3.4 getObjectType
// MapperFactoryBean.java
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
3.5 isSingleton
// MapperFactoryBean.java
@Override
public boolean isSingleton() {
return true;
}
4. @MapperScan
艿艿:本小节,需要胖友对 Spring IOC 有一定的了解。如果不熟悉的胖友,建议不需要特别深入的理解。或者说,先去看 《精尽 Spring 源码解析》 。
org.mybatis.spring.annotation.@MapperScan
注解,指定需要扫描的包,将包中符合的 Mapper 接口,注册成 beanClass
为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象。代码如下:
// MapperScan.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise
* annotation declarations e.g.:
* {@code @MapperScan("org.my.pkg")} instead of {@code @MapperScan(basePackages = "org.my.pkg"})}.
*
* 和 {@link #basePackages()} 相同意思
*
* @return base package names
*/
String[] value() default {};
/**
* Base packages to scan for MyBatis interfaces. Note that only interfaces
* with at least one method will be registered; concrete classes will be
* ignored.
*
* 扫描的包地址
*
* @return base package names for scanning mapper interface
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages
* to scan for annotated components. The package of each class specified will be scanned.
* <p>Consider creating a special no-op marker class or interface in each package
* that serves no purpose other than being referenced by this attribute.
*
* @return classes that indicate base package for scanning mapper interface
*/
Class<?>[] basePackageClasses() default {};
/**
* The {@link BeanNameGenerator} class to be used for naming detected components
* within the Spring container.
*
* @return the class of {@link BeanNameGenerator}
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* This property specifies the annotation that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have
* the specified annotation.
* <p>
* Note this can be combined with markerInterface.
*
* 指定注解
*
* @return the annotation that the scanner will search for
*/
Class<? extends Annotation> annotationClass() default Annotation.class;
/**
* This property specifies the parent that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have
* the specified interface class as a parent.
* <p>
* Note this can be combined with annotationClass.
*
* 指定接口
*
* @return the parent that the scanner will search for
*/
Class<?> markerInterface() default Class.class;
/**
* Specifies which {@code SqlSessionTemplate} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.
*
* 指向的 SqlSessionTemplate 的名字
*
* @return the bean name of {@code SqlSessionTemplate}
*/
String sqlSessionTemplateRef() default "";
/**
* Specifies which {@code SqlSessionFactory} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.
*
* 指向的 SqlSessionFactory 的名字
*
* @return the bean name of {@code SqlSessionFactory}
*/
String sqlSessionFactoryRef() default "";
/**
* Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean.
*
* 可自定义 MapperFactoryBean 的实现类
*
* @return the class of {@code MapperFactoryBean}
*/
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
- 属性比较多,实际上,胖友常用的就是
value()
属性。 - 重点是
@Import(MapperScannerRegistrar.class)
。为什么呢?@Import
注解,负责资源的导入。如果导入的是一个 Java 类,例如此处为 MapperScannerRegistrar 类,Spring 会将其注册成一个 Bean 对象。而 MapperScannerRegistrar 类呢?详细解析,见 「4.1 MapperScannerRegistrar」 。
使用示例如下:
@Configuration
@ImportResource("classpath:org/mybatis/spring/sample/config/applicationContext-infrastructure.xml")
@MapperScan("org.mybatis.spring.sample.mapper") // here
static class AppConfig {
}
- 该示例来自
org.mybatis.spring.sample.SampleEnableTest
单元测试。胖友可以基于它调试。
4.1 MapperScannerRegistrar
org.mybatis.spring.annotation.MapperScannerRegistrar
,实现 ImportBeanDefinitionRegistrar、ResourceLoaderAware 接口,@MapperScann
的注册器,负责将扫描到的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象。
4.1.1 构造方法
// MapperScannerRegistrar.java
/**
* ResourceLoader 对象
*/
private ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
- 因为实现了 ResourceLoaderAware 接口,所以
resourceLoader
属性,能够被注入。
4.1.2 registerBeanDefinitions
// MapperScannerRegistrar.java
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// <1> 获得 @MapperScan 注解信息
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
// <2> 扫描包,将扫描到的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象
registerBeanDefinitions(mapperScanAttrs, registry);
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
// <3.1> 创建 ClassPathMapperScanner 对象
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// <3.2> 设置 resourceLoader 属性到 scanner 中
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
// <3.3> 获得 @(MapperScan 注解上的属性,设置到 scanner 中
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
// <4> 获得要扫描的包
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")) // 包
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("basePackages")) // 包
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getClassArray("basePackageClasses")) // 类
.map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
// <5> 注册 scanner 的过滤器
scanner.registerFilters();
// <6> 执行扫描
scanner.doScan(StringUtils.toStringArray(basePackages));
}
-
<1>
处,获得@MapperScan
注解信息。 -
<2>
处,调用#registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry)
方法,扫描包,将扫描到的 Mapper 接口,注册成beanClass
为 MapperFactoryBean 的 BeanDefinition 对象。 -
<3.1>
处,创建 ClassPathMapperScanner 对象。下面的代码,都是和 ClassPathMapperScanner 相关。所以,详细的解析,最终看
「4.2 ClassPathMapperScanner」
。
<3.2>
处, 设置resourceLoader
属性到scanner
中。<3.3>
处,获得@MapperScan
注解上的属性,设置到scanner
中。
-
<4>
处,获得要扫描的包。 -
<5>
处,调用ClassPathMapperScanner#registerFilters()
方法,注册 scanner 的过滤器。 -
<6>
处,调用ClassPathMapperScanner#doScan(String... basePackages)
方法,执行扫描,将扫描到的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象。 -
😈 上面看了一堆的 ClassPathMapperScanner 类的调用,下面开始我们的旅程。
4.2 ClassPathMapperScanner
org.mybatis.spring.mapper.ClassPathMapperScanner
,继承 org.springframework.context.annotation.ClassPathMapperScanner
类,负责执行扫描,将扫描到的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象。
可能很多胖友不熟悉 ClassPathMapperScanner 类,可以看看 《Spring自定义类扫描器》 文章。哈哈哈,艿艿也是现学的。
4.2.1 构造方法
// ClassPathMapperScanner.java
/**
* 是否添加到 {@link org.apache.ibatis.session.Configuration} 中
*/
private boolean addToConfig = true;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
/**
* {@link #sqlSessionTemplate} 的 bean 名字
*/
private String sqlSessionTemplateBeanName;
/**
* {@link #sqlSessionFactory} 的 bean 名字
*/
private String sqlSessionFactoryBeanName;
/**
* 指定注解
*/
private Class<? extends Annotation> annotationClass;
/**
* 指定接口
*/
private Class<?> markerInterface;
/**
* MapperFactoryBean 对象
*/
private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<>();
// ... 省略 setting 方法
4.2.2 registerFilters
#registerFilters()
方法,注册过滤器。代码如下:
// ClassPathMapperScanner.java
/**
* Configures parent scanner to search for the right interfaces. It can search
* for all interfaces or just for those that extends a markerInterface or/and
* those annotated with the annotationClass
*
* 注册过滤器
*/
public void registerFilters() {
boolean acceptAllInterfaces = true; // 是否接受所有接口
// if specified, use the given annotation and / or marker interface
// 如果指定了注解,则添加 INCLUDE 过滤器 AnnotationTypeFilter 对象
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false; // 标记不是接受所有接口
}
// override AssignableTypeFilter to ignore matches on the actual marker interface
// 如果指定了接口,则添加 INCLUDE 过滤器 AssignableTypeFilter 对象
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false; // 标记不是接受所有接口
}
// 如果接受所有接口,则添加自定义 INCLUDE 过滤器 TypeFilter ,全部返回 true
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
// 添加 INCLUDE 过滤器,排除 package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
- 根据配置,添加 INCLUDE 和 EXCLUDE 过滤器。
4.2.3 doScan
#doScan(String... basePackages)
方法,执行扫描,将扫描到的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象。代码如下:
// ClassPathMapperScanner.java
/**
* Calls the parent search that will search and register all the candidates.
* Then the registered objects are post processed to set them as
* MapperFactoryBeans
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// <1> 执行扫描,获得包下符合的类们,并分装成 BeanDefinitionHolder 对象的集合
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 {
// 处理 BeanDefinitionHolder 对象的集合
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
-
<1>
处,调用父ClassPathBeanDefinitionScanner#doScan(basePackages)
方法,执行扫描,获得包下符合的类们,并分装成 BeanDefinitionHolder 对象的集合。 -
<2>
处,调用#processBeanDefinitions((Set<BeanDefinitionHolder> beanDefinitions)
方法,处理 BeanDefinitionHolder 对象的集合。代码如下:// ClassPathMapperScanner.java private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; // <1> 遍历 BeanDefinitionHolder 数组,逐一设置属性 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 // <2> 此处 definition 的 beanClass 为 Mapper 接口,需要修改成 MapperFactoryBean 类,从而创建 Mapper 代理对象 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 definition.setBeanClass(this.mapperFactoryBean.getClass()); // <3> 设置 `MapperFactoryBean.addToConfig` 属性 definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; // <4.1>是否已经显式设置了 sqlSessionFactory 或 sqlSessionFactory 属性 // <4.2> 如果 sqlSessionFactoryBeanName 或 sqlSessionFactory 非空,设置到 `MapperFactoryBean.sqlSessionFactory` 属性 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; } // <4.3> 如果 sqlSessionTemplateBeanName 或 sqlSessionTemplate 非空,设置到 `MapperFactoryBean.sqlSessionTemplate` 属性 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; } // <4.4> 如果未显式设置,则设置根据类型自动注入 if (!explicitFactoryUsed) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } }
-
虽然方法很长,重点就是修改 BeanDefinitionHolder 的相关属性。
-
<1>
处,遍历 BeanDefinitionHolder 数组,逐一设置属性。 -
<2>
处,此处definition
的beanClass
为 Mapper 接口,需要修改成 MapperFactoryBean 类,从而创建 Mapper 代理对象。 -
<3>
处,设置MapperFactoryBean.addToConfig
属性。 -
<4.1>
处,是否已经显式设置了
sqlSessionFactory 或 sqlSessionFactory
属性。
<4.2>
处,如果sqlSessionFactoryBeanName
或sqlSessionFactory
非空,设置到MapperFactoryBean.sqlSessionFactory
属性。<4.3>
处,如果sqlSessionTemplateBeanName
或sqlSessionTemplate
非空,设置到MapperFactoryBean.sqlSessionTemplate
属性。<4.4>
处,如果未显式设置,则设置根据类型自动注入。
-
4.3 @MapperScans
org.mybatis.spring.annotation.@MapperScans
,多 @MapperScan
的注解,功能是相同的。代码如下:
// MapperScans.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.RepeatingRegistrar.class)
public @interface MapperScans {
/**
* @return @MapperScan 数组
*/
MapperScan[] value();
}
- 此处,
@Import(MapperScannerRegistrar.RepeatingRegistrar.class)
是 RepeatingRegistrar 类。
4.4 RepeatingRegistrar
RepeatingRegistrar ,是 MapperScannerRegistrar 的内部静态类,继承 MapperScannerRegistrar 类,@MapperScans
的注册器。代码如下:
// MapperScannerRegistrar.java
static class RepeatingRegistrar extends MapperScannerRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获得 @MapperScans 注解信息
AnnotationAttributes mapperScansAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
// 遍历 @MapperScans 的值,调用 `#registerBeanDefinitions(mapperScanAttrs, registry)` 方法,循环扫描处理
Arrays.stream(mapperScansAttrs.getAnnotationArray("value"))
.forEach(mapperScanAttrs -> registerBeanDefinitions(mapperScanAttrs, registry));
}
}
5. 自定义 <mybatis:scan />
标签
使用示例如下:
<!-- Scan for mappers and let them be autowired; notice there is no
UserDaoImplementation needed. The required SqlSessionFactory will be
autowired. -->
<mybatis:scan base-package="org.mybatis.spring.sample.mapper" />
- 该示例来自
org.mybatis.spring.sample.SampleNamespaceTest
单元测试。胖友可以基于它调试。
简单来理解,<mybatis:scan />
标签,和 @MapperScan
注解,用途是等价的。
5.1 spring.schemas
MyBatis 在 META-INF/spring.schemas
定义如下:
http\://mybatis.org/schema/mybatis-spring-1.2.xsd=org/mybatis/spring/config/mybatis-spring.xsd
http\://mybatis.org/schema/mybatis-spring.xsd=org/mybatis/spring/config/mybatis-spring.xsd
- xmlns 为
http://mybatis.org/schema/mybatis-spring-1.2.xsd
或http://mybatis.org/schema/mybatis-spring.xsd
。 - xsd 为
org/mybatis/spring/config/mybatis-spring.xsd
。
5.2 mybatis-spring.xsd
mybatis-spring.xsd定义如下:mybatis-spring.xsd
5.3 spring.handler
spring.handlers
定义如下:
http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler
定义了 MyBatis 的 XML Namespace 的处理器 NamespaceHandler 。
5.4 NamespaceHandler
org.mybatis.spring.config.NamespaceHandler
,继承 NamespaceHandlerSupport 抽象类,MyBatis 的 XML Namespace 的处理器。代码如下:
// NamespaceHandler.java
public class NamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser());
}
}
<mybatis:scan />
标签,使用 MapperScannerBeanDefinitionParser 解析。
5.5 MapperScannerBeanDefinitionParser
org.mybatis.spring.config.MapperScannerBeanDefinitionParser
,实现 BeanDefinitionParser 接口,<mybatis:scan />
的解析器。代码如下:
// BeanDefinitionParser.java
public class MapperScannerBeanDefinitionParser implements BeanDefinitionParser {
private static final String ATTRIBUTE_BASE_PACKAGE = "base-package";
private static final String ATTRIBUTE_ANNOTATION = "annotation";
private static final String ATTRIBUTE_MARKER_INTERFACE = "marker-interface";
private static final String ATTRIBUTE_NAME_GENERATOR = "name-generator";
private static final String ATTRIBUTE_TEMPLATE_REF = "template-ref";
private static final String ATTRIBUTE_FACTORY_REF = "factory-ref";
/**
* {@inheritDoc}
*/
@Override
public synchronized BeanDefinition parse(Element element, ParserContext parserContext) {
// 创建 ClassPathMapperScanner 对象
ClassPathMapperScanner scanner = new ClassPathMapperScanner(parserContext.getRegistry());
ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
XmlReaderContext readerContext = parserContext.getReaderContext();
scanner.setResourceLoader(readerContext.getResourceLoader()); // 设置 resourceLoader 属性
try {
// 解析 annotation 属性
String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION);
if (StringUtils.hasText(annotationClassName)) {
@SuppressWarnings("unchecked")
Class<? extends Annotation> markerInterface = (Class<? extends Annotation>) classLoader.loadClass(annotationClassName);
scanner.setAnnotationClass(markerInterface);
}
// 解析 marker-interface 属性
String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE);
if (StringUtils.hasText(markerInterfaceClassName)) {
Class<?> markerInterface = classLoader.loadClass(markerInterfaceClassName);
scanner.setMarkerInterface(markerInterface);
}
// 解析 name-generator 属性
String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR);
if (StringUtils.hasText(nameGeneratorClassName)) {
Class<?> nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName);
BeanNameGenerator nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class);
scanner.setBeanNameGenerator(nameGenerator);
}
} catch (Exception ex) {
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
}
// 解析 template-ref 属性
String sqlSessionTemplateBeanName = element.getAttribute(ATTRIBUTE_TEMPLATE_REF);
scanner.setSqlSessionTemplateBeanName(sqlSessionTemplateBeanName);
// 解析 factory-ref 属性
String sqlSessionFactoryBeanName = element.getAttribute(ATTRIBUTE_FACTORY_REF);
scanner.setSqlSessionFactoryBeanName(sqlSessionFactoryBeanName);
// 注册 scanner 的过滤器
scanner.registerFilters();
// 获得要扫描的包
String basePackage = element.getAttribute(ATTRIBUTE_BASE_PACKAGE);
// 执行扫描
scanner.scan(StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
return null;
}
}
- 代码实现上,和 「4.2 ClassPathMapperScanner」 是基本一致的。所以就不详细解析啦。
6. MapperScannerConfigurer
org.mybatis.spring.mapper.MapperScannerConfigurer
,实现 BeanDefinitionRegistryPostProcessor、InitializingBean、ApplicationContextAware、BeanNameAware 接口,定义需要扫描的包,将包中符合的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象。
使用示例如下:
// XML
<!-- Scan for mappers and let them be autowired; notice there is no
UserDaoImplementation needed. The required SqlSessionFactory will be
autowired. -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
</bean>
- 该示例来自
org.mybatis.spring.sample.MapperScannerConfigurer
单元测试。胖友可以基于它调试。
6.1 构造方法
// MapperScannerConfigurer.java
private String basePackage;
private boolean addToConfig = true;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
private String sqlSessionFactoryBeanName;
private String sqlSessionTemplateBeanName;
private Class<? extends Annotation> annotationClass;
private Class<?> markerInterface;
private ApplicationContext applicationContext;
private String beanName;
private boolean processPropertyPlaceHolders;
private BeanNameGenerator nameGenerator;
// 省略 setting 方法
6.2 afterPropertiesSet
// MapperScannerConfigurer.java
@Override
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
- 啥都不做。
6.3 postProcessBeanFactory
// MapperScannerConfigurer.java
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// left intentionally blank
}
6.4 postProcessBeanDefinitionRegistry
// MapperScannerConfigurer.java
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// <1> 如果有属性占位符,则进行获得,例如 ${basePackage} 等等
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// <2> 创建 ClassPathMapperScanner 对象,并设置其相关属性
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 过滤器
scanner.registerFilters();
// 执行扫描
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
-
<1>
处,调用#processPropertyPlaceHolders()
方法,如果有属性占位符,则进行获得,例如${basePackage}
等等。代码如下:// MapperScannerConfigurer.java private void processPropertyPlaceHolders() { Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) { BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext) .getBeanFactory().getBeanDefinition(beanName); // PropertyResourceConfigurer does not expose any methods to explicitly perform // property placeholder substitution. Instead, create a BeanFactory that just // contains this mapper scanner and post process the factory. 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); } } // 获得属性值,并转换成 String 类型 private String updatePropertyValue(String propertyName, PropertyValues values) { PropertyValue property = values.getPropertyValue(propertyName); Object value = property.getValue(); if (value instanceof String) { return value.toString(); } else if (value instanceof TypedStringValue) { return ((TypedStringValue) value).getValue(); } else { return null; } }
-
<2>
处,代码实现上,和 「4.2 ClassPathMapperScanner」 是基本一致的。所以就不详细解析啦。
666. 彩蛋
略微冗长,但是易于理解的一篇文章。
我们简单把 3 ~ 6 小节做个小结的话:
- 「3」MapperFactoryBean 类,是最基础的、单个的负责创建 Mapper 代理对象的类。
- 「4」「5」「6」,都是基于 MapperFactoryBean 之上,使用 ClassPathMapperScanner 扫描指定包,创建成 MapperFactoryBean 对象,从而创建 Mapper 代理对象。