目录
1.applicationContext.xml配置文件改动
3.ClassPathBeanDefinitionScanner
一、Mybatis和Spring集成项目准备
学习本篇前可先看(五)Mybatis持久化框架原理之MapperFactoryBean方式和Spring集成,了解最基本的mybatis-spring集成方式后再来学习此方式效果更好。
看过MapperFactoryBean和Spring的集成方式就可以知道,针对每个mapper接口需要单独写一个对应的配置,如果mapper接口数量很多,就会显得很麻烦,因此Mybatis提供了扫描包的方式来完成读取并封装mapper接口,主要类是MapperScannerConfigurer。
1.applicationContext.xml配置文件改动
除了spring配置文件有改动以外,其他的都和上一篇一致即可,无需改动。
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 引入properties文件 -->
<context:property-placeholder location="classpath:database.properties"/>
<!-- 定义数据源 -->
<bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource">
<property name="driverType" value="${connection.driver}"/>
<property name="URL" value="${connection.url}"/>
<property name="user" value="${connection.username}"/>
<property name="password" value="${connection.password}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- mybatis 配置文件 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 指定实体类映射文件,可以同时指定某一包以及子包下面的所有配置文件,
mapperLocations和configLocation有一个即可,当需要为实体类指定别名时,
可指定configLocation属性,再在mybatis总配置文件中采用mapper引入实体类映射文件 -->
<property name="mapperLocations" value="classpath*:*Mapper.xml"/>
</bean>
<!-- 使用MapperFactoryBean方式和spring集成, -->
<!--bean id="demoMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.iboxpay.mapper.DemoMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean-->
<!-- 使用MapperScannerConfigurer方式和spring集成 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.iboxpay.mapper"/>
<!-- 可有可无,不影响程序的正常使用 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>
二、UML类图和流程分析
1.UML类图分析
下图是此方法的UML类图结构:
对于SqlSessionFactoryBean、MapperFactoryBean、SqlSessionTemplate这三个类以及其实现的接口在此篇不做过多的介绍,此篇只需要重点关注MapperScannerConfigurer类,其涉及的几个接口介绍分别如下:
- ApplicationContextAware接口:spring框架在运行的时候会调用此接口的setApplicationContext方法,通过该方法可以将spring的上下文赋给实现类,但前提是spring需要把该类当成一个bean;
- BeanNameAware接口:spring框架运行时调用此接口的setBeanName,为bean赋值名字;
- BeanFactoryPostProcessor接口:用来修改spring上下文中的BeanFactory,允许在bean未实例化之前对bean进行添加属性或者重写的操作;
- BeanDefinitionRegistryPostProcessor接口:用来修改spring上下文中的bean定义注册中心BeanDefinitionRegistry,允许下一个post-processor开始前为该注册中心添加更多的bean定义。
2.流程分析
其流程图如下:
可以看到其相较于MapperFactoryBean的集成方式只多了一层MapperScannerConfigurer,这个类的主要作用便是扫描basePackage配置下的mapper接口,并将接口使用MapperFactoryBean作为其FactoryBean,在Spring工厂实例化该mapper时,将会调用MapperFactoryBean的getObject,从而完成使用包集成的方式替换了配置多个MapperFactoryBean的对应关系。
需要注意的是MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,其实际实现了BeanFactoryPostProcessor接口,因此这些类会比一般的Bean初始化的早,且会被调用的比其它普通Bean早。
三、原理分析
1.SqlSessionFactoryBean
该类的操作流程在(五)Mybatis持久化框架原理之MapperFactoryBean方式和Spring集成文中已经写过,这里便不再复述。
2.MapperScannerConfigurer
该类在applicationContext.xml文件中赋值结束后将会被spring容器实例化,同时在实例化完该类后,将会调用BeanDefinitionRegistryPostProcessor接口方法postProcessBeanDefinitionRegistry,该类做的一些初始化操作就是在此方法中进行的,接下来看下该类的源码:
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor,
InitializingBean, ApplicationContextAware, BeanNameAware {
// XML文件中配置的基础扫描包
private String basePackage;
// 将会被传入MapperFactoryBean中
private boolean addToConfig = true;
// 是否延迟加载
private String lazyInitialization;
// 对应的sqlSessionFactory对象
private SqlSessionFactory sqlSessionFactory;
// 对应的sqlSessionTemplate对象
private SqlSessionTemplate sqlSessionTemplate;
// 忽略其它不重要的成员属性和方法
...
@Override
public void afterPropertiesSet() throws Exception {
// 在被实例化后将被调用,检查基础扫描包basePackage属性是否为空
notNull(this.basePackage, "Property 'basePackage' is required");
}
@Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) {
// BeanDefinitionRegistryPostProcessor有两个方法,但是只需要用到一个
// 因此这个方法留白
}
@Override
public void postProcessBeanDefinitionRegistry(
BeanDefinitionRegistry registry) {
// 是否处理属性占位符,实际上一般而言这个都不会被用到,因为
// processPropertyPlaceHolders默认为false,正常的使用不会进入判断方法体
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);
// 如果mapperFactoryBeanClass为空,默认为MapperFactoryBean类型
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));
}
}
当在applicationContext.xml文件中对MapperScannerConfigurer bean对象进行set赋值之后,afterPropertiesSet方法中将会对basePackage属性进行为空校验,因此可以看出来,该bean的配置只有该属性是必须的,其他的属性都可配可不配,通过之后就会调用postProcessBeanFactory方法和postProcessBeanDefinitionRegistry方法,在此类中,postProcessBeanFactory方法为空,因此只需要把注意点放在postProcessBeanDefinitionRegistry方法上,接下来对该方法进行详细的分析:
- 如果processPropertyPlaceHolders属性为true,则会进入processPropertyPlaceHolders方法,在此方法中会将再次从spring容器中拿取beanName相同的bean定义,并从该bean定义中拿取诸如basePackage、sqlSessionFactoryBeanName、sqlSessionTemplateBeanName、lazyInitialization等属性覆盖MapperScannerConfigurer中的属性;
- 将MapperScannerConfigurer类中的addToConfig、markerInterface、sqlSessionFactory、sqlSessionTemplate、sqlSessionFactoryBeanName、sqlSessionTemplateBeanName、applicationContext、mapperFactoryBeanClass等属性赋值给ClassPathMapperScanner对象;
- registerFilters注册过滤器,如果annotationClass和markerInterface都为空,则默认不设置过滤器;
3.ClassPathBeanDefinitionScanner
这个类的作用便是根据传进来的各种参数对包进行扫描,随后再将扫描获得的对象进行某种处理,具体是如何操作的一起来看看关键源码部分:
public class ClassPathBeanDefinitionScanner
extends ClassPathScanningCandidateComponentProvider {
// spring注册中心
private final BeanDefinitionRegistry registry;
// 这个方法在MapperScannerConfigurer类中被调用,实际上调用的是子类
// ClassPathMapperScanner,但是其并没有重写或重载这个方法,因此会调用
// 父类的scan方法
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 调用本类的soScan方法
doScan(basePackages);
if (this.includeAnnotationConfig) {
AnnotationConfigUtils
.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() -
beanCountAtScanStart);
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions =
new LinkedHashSet<BeanDefinitionHolder>();
// 多个包依次进行解析
for (String basePackage : basePackages) {
// 根据传入的包路径,扫描该包下的类,同时会根据isCandidateComponent
// 方法判断什么样的类会被扫描到,该流程实际类是ClassPathMapperScanner
// 其方法的判断条件为只有接口才能被扫描进来
Set<BeanDefinition> candidates =
findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
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);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils
.processCommonDefinitionAnnotations(
(AnnotatedBeanDefinition) candidate);
}
// 判断注册中心是否含有该候选类,如果没有则返回true
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder =
new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils
.applyScopedProxyMode(scopeMetadata,
definitionHolder, this.registry);
// 将候选类封装成Bean定义,且注册到Spring注册中心称为Bean
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
// 返回Bean定义集合
return beanDefinitions;
}
}
这个类是ClassPathMapperScanner的父类,因此等下分析ClassPathMapperScanner类的父类方法时,只需要知道其大致作用即可。
4.ClassPathMapperScanner
在MapperScannerConfigurer类中调用的实际上是ClassPathMapperScanner类,在前面我们已经知道了scan方法后面会调用到doScan方法来,因此我们看看其关键源码:
public class ClassPathMapperScanner
extends ClassPathBeanDefinitionScanner {
// 上面的成员属性不做过多介绍,实际上和MapperScannerConfigurer类中设置的
// 属性差不多
@Override
protected boolean isCandidateComponent(
AnnotatedBeanDefinition beanDefinition) {
// 用来判断父类doScan时哪些类符合条件
return beanDefinition.getMetadata().isInterface() &&
beanDefinition.getMetadata().isIndependent();
}
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass =
MapperFactoryBean.class;
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类获得候选Bean定义
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
// 为空则不做什么
} else {
// 不为空则调用该方法
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(
Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
// 遍历获得的mapper接口候选bean定义
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
// 使用MapperFactoryBean来封装原来的bean
definition.getConstructorArgumentValues()
.addGenericArgumentValue(beanClassName);
definition.setBeanClass(this.mapperFactoryBeanClass);
// 下面则是设置一系列属性,诸如addToConfig、sqlSessionFactory、
// lazyInitialization和AutowireMode等
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 (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
}
}
}
至此,我们便已经解析了config.xml文件和mapper.xml文件,且把mapper接口使用MapperFactoryBean封装了起来注册到了Spring工厂中,在后续使用时直接使用@Autowired自动注入则可以完成mapper接口的调用。