- 前言
- 第一部分 核心实现
- 第 1 章 Spring 整体架构和环境搭建
- 第 2 章 容器的基本实现
- 第 3 章 默认标签的解析
- 第 4 章 自定义标签的解析
- 第 5 章 bean 的加载
- 第 6 章 容器的功能扩展
- 第 7 章 AOP
- 第二部分 企业应用
- 第 8 章 数据库连接 JDBC
- 第 9 章 整合 MyBatis
- 第 10 章 事务
- 第 11 章 SpringMVC
- 第 12 章 远程服务
- 第 13 章 Spring 消息
9.3.3 MapperScannerConfigurer
我们在 applicationContext.xml 中配置了 userMapper 供需要时使用。但如果需要用到的映射器较多的话,采用这种配置方式就显得很低效。为了解决这个问题,我们可以使用 MapperScanner Configurer,让它扫描特定的包,自动帮我们成批地创建映射器。这样一来,就能大大减少配置的工作量,比如我们将 applicationContext.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.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/lexueba?useUnicode=
true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull"></property>
<property name="username" value="root"></property>
<property name="password" value="haojia0421xixi"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.Spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:test/mybatis/MyBatis- Configuration.
xml"></property>
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="aaaaa"/>
</bean>
<!-- 注释掉原有代码
<bean id="userMapper" class="org.mybatis.Spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="test.mybatis.dao.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
-->
<bean class="org.mybatis.Spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="test.mybatis.dao" />
</bean>
</beans>
在上面的配置中,我们屏蔽掉了最原始的代码( userMapper 的创建)而增加了 MapperScannerConfigurer 的配置,basePackage 属性是让你为映射器接口文件设置基本的包路径。你可以使用分号或逗号作为分隔符设置多于一个的包路径。每个映射器将会在指定的包路径中递归地被搜索到。被发现的映射器将会使用 Spring 对自动侦测组件默认的命名策略来命名。也就是说,如果没有发现注解,它就会使用映射器的非大写的非完全限定类名。但是如果发现了 @Component 或 JSR-330@Named 注解,它会获取名称。
通过上面的配置, Spring 就会帮助我们对 test. mybatis.dao 下面的所有接口进行自动的注入,而不需要为每个接口重复在 Spring 配置文件中进行声明了。那么,这个功能又是如何做到的呢?MapperScanner Configurer 中又有哪些核心操作呢?同样,首先查看类的层次结构图,如图 9-4 所示。
图 9-4 MapperScannerConfigurer 类的层次结构图
我们又看到了令人感兴趣的接口 InitializingBean,马上查找类的 afterPropertiesSet 方法来看看类的初始化逻辑。
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
很遗憾,分析并没有想我们之前那样顺利,afterPropertiesSet() 方法除了一句对 basePackage 属性的验证代码外并没有太多的逻辑实现。好吧,让我们回过头再次查看 MapperScanner Configurer 类层次结构图中感兴趣的接口。于是,我们发现了 BeanDefinitionRegistryPostProcessor 与 BeanFactoryPostProcessor,Spring 在初始化的过程中同样会保证这两个接口的调用。
首先查看 MapperScannerConfigurer 类中对于 BeanFactoryPostProcessor 接口的实现:
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// left intentionally blank
}
没有任何逻辑实现,只能说明我们找错地方了,继续找,查看 MapperScannerConfigurer 类中对于 BeanDefinitionRegistryPostProcessor 接口的实现。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws
BeansException {
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.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, Configurable
ApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
Bingo!这次找对地方了。大致看一下代码实现,正是完成了对指定路径扫描的逻辑。那么,我们就以此为入口,详细地分析 MapperScannerConfigurer 所提供的逻辑实现。
1.processPropertyPlaceHolders 属性的处理
首先,难题就是 processPropertyPlaceHolders 属性的处理。或许读者并未过多接触此属性,我们只能查看 processPropertyPlaceHolders() 函数来反推此属性所代表的功能。
/*
* BeanDefinitionRegistries are called early in application startup, before
* BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been
* loaded and any property substitution of this class' properties will fail. To avoid
this, find
* any PropertyResourceConfigurers defined in the context and run them on this class' bean
* definition. Then update the values.
*/
private void processPropertyPlaceHolders() {
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType
(PropertyResourceConfigurer.class);
if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {
BeanDefinition mapperScannerBean = ((GenericApplicationContext) 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);
}
}
不知读者是否悟出了此函数的作用呢?或许此函数的说明会给我们一些提示:BeanDefinitionRegistries 会在应用启动的时候调用,并且会早于 BeanFactoryPostProcessors 的调用,这就意味着 PropertyResourceConfigurers 还没有被加载所有对于属性文件的引用将会失效。为避免此种情况发生,此方法手动地找出定义的 PropertyResourceConfigurers 并进行提前调用以保证对于属性的引用可以正常工作。
我想读者已经有所感悟,结合之前讲过的 PropertyResourceConfigurer 的用法,举例说明一下,如要创建配置文件如 test.properties,并添加属性对:
basePackage=test.mybatis.dao
然后在 Spring 配置文件中加入属性文件解析器:
<bean id="mesHandler" class="org.Springframework.beans.factory.config.Property Placeholder
Configurer">
<property name="locations">
<list>
<value>config/test.properties</value>
</list>
</property>
</bean>
修改 MapperScannerConfigurer 类型的 bean 的定义:
<bean class="org.mybatis.Spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="${basePackage}" />
</bean>
此时你会发现,这个配置并没有达到预期的效果,因为在解析${basePackage}的时候 PropertyPlaceholderConfigurer 还没有被调用,也就是属性文件中的属性还没有加载至内存中, Spring 还不能直接使用它。为了解决这个问题,Spring 提供了 processPropertyPlaceHolders 属性,你需要这样配置 MapperScannerConfigurer 类型的 bean。
<bean class="org.mybatis.Spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="test.mybatis.dao" />
<property name="processPropertyPlaceHolders" value="true" />
</bean>
通过 processPropertyPlaceHolders 属性的配置,将程序引入我们正在分析的 processProperty PlaceHolders 函数中来完成属性文件的加载。至此,我们终于理清了这个属性的作用,再次回顾这个函数所做的事情。
(1)找到所有已经注册的 PropertyResourceConfigurer 类型的 bean。
(2)模拟 Spring 中的环境来用处理器。这里通过使用 new DefaultListableBeanFactory() 来模拟 Spring 中的环境(完成处理器的调用后便失效),将映射的 bean,也就是 MapperScanner Configurer 类型 bean 注册到环境中来进行后理器的调用,处理器 PropertyPlaceholderConfigurer 调用完成的功能,即找出所有 bean 中应用属性文件的变量并替换。也就是说,在处理器调用后,模拟环境中模拟的 MapperScannerConfigurer 类型的 bean 如果有引入属性文件中的属性那么已经被替换了,这时,再将模拟 bean 中相关的属性提取出来应用在真实的 bean 中。
2.根据配置属性生成过滤器
在 postProcessBeanDefinitionRegistry 方法中可以看到,配置中支持很多属性的设定,但是我们感兴趣的或者说影响扫描结果的并不多,属性设置后通过在 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(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadata
ReaderFactory) throws IOException {
return true;
}
});
}
//不扫描 package-info.java 文件
addExcludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadata
ReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}
代码中得知,根据之前属性的配置生成了对应的过滤器。
(1)annotationClass 属性处理。
如果 annotationClass 不为空,表示用户设置了此属性,那么就要根据此属性生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是 AnnotationTypeFilter。Annotation TypeFilter 保证在扫描对应 Java 文件时只接受标记有注解为 annotationClass 的接口。
(2)markerInterface 属性处理。
如果 markerInterface 不为空,表示用户设置了此属性,那么就要根据此属性生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是实现 AssignableTypeFilter 接口的局部类。表示扫描过程中只有实现 markerInterface 接口的接口才会被接受。
(3)全局默认处理。
在上面两个属性中如果存在其中任何属性,acceptAllInterfaces 的值将会变改变,但是如果用户没有设定以上两个属性,那么,Spring 会为我们增加一个默认的过滤器实现 TypeFilter 接口的局部类,旨在接受所有接口文件。
(4)package-info.java 处理。
对于命名为 package-info 的 Java 文件,默认不作为逻辑实现接口,将其排除掉,使用 TypeFilter 接口的局部类实现 match 方法。
从上面的函数我们看出,控制扫描文件 Spring 通过不同的过滤器完成,这些定义的过滤器记录在了 includeFilters 和 excludeFilters 属性中。
public void addIncludeFilter(TypeFilter includeFilter) {
this.includeFilters.add(includeFilter);
}
public void addExcludeFilter(TypeFilter excludeFilter) {
this.excludeFilters.add(0, excludeFilter);
}
至于过滤器为什么会在扫描过程中起作用,我们在讲解扫描实现时候再继续深入研究。
3.扫描 Java 文件
设置了相关属性以及生成了对应的过滤器后便可以进行文件的扫描了,扫描工作是由 ClassPathMapperScanner 类型的实例 scanner 中的 scan 方法完成的。
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
//如果配置了 includeAnnotationConfig,则注册对应注解的处理器以保证注解功能的正常使用。
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}
scan 是个全局方法,扫描工作通过 doScan(basePackages) 委托给了 doScan 方法,同时,还包括了 includeAnnotationConfig 属性的处理,AnnotationConfigUtils.registerAnnotation ConfigProcessors (this.registry) 代码主要是完成对于注解处理器的简单注册,比如 AutowiredAnnotationBeanPost Processor、RequiredAnnotationBeanPostProcessor 等,这里不再赘述,我们重点研究文件扫描功能的实现。
ClassPathMapperScanner.java
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
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 {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
//开始构造 MapperFactoryBean 类型的 bean.
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
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) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" +
holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
此时,虽然还没有完成介绍到扫描的过程,但是我们也应该理解了 Spring 中对于自动扫描的注册,声明 MapperScannerConfigurer 类型的 bean 目的是不需要我们对于每个接口都注册一个 MapperFactoryBean 类型的对应的 bean,但是,不在配置文件中注册并不代表这个 bean 不存在,而是在扫描的过程中通过编码的方式动态注册。实现过程我们在上面的函数中可以看得非常清楚。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet <BeanDefinition
Holder>();
for (String basePackage : basePackages) {
//扫描 basePackage 路径下 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);
}
if (candidate instanceof AnnotatedBeanDefinition) {
//如果是 AnnotatedBeanDefinition 类型的 bean,需要检测下常用注解如:
Primary、Lazy 等
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;
}
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
String packageSearchPath = ResourcePatternResolver. CLASSPATH_ ALL_URL_
PREFIX +
resolveBasePackage(basePackage) + "/" + this.resourcePattern;
Resource[] resources = this.resourcePatternResolver.getResources (package
SearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.
getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBean
Definition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component
class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete
top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any
filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath
scanning", ex);
}
return candidates;
}
findCandidateComponents 方法根据传入的包路径信息并结合类文件路径拼接成文件的绝对路径,同时完成了文件的扫描过程并且根据对应的文件生成了对应的 bean ,使用 ScannedGenericBeanDefinition 类型的 bean 承载信息,bean 中只记录了 resource 和 source 信息。这里,我们更感兴趣的是 isCandidateComponent(metadataReader),此句代码用于判断当前扫描的文件是否符合要求,而我们之前注册的一些过滤器信息也正是在此时派上用场的。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
if (!metadata.isAnnotated(Profile.class.getName())) {
return true;
}
AnnotationAttributes profile = MetadataUtils.attributesFor(metadata,
Profile.class);
return this.environment.acceptsProfiles(profile.getStringArray("value"));
}
}
return false;
}
我们看到了之前加入过滤器的两个属性 excludeFilters、includeFilters,并且知道对应的文件是否符合要求是根据过滤器中的 match 方法所返回的信息来判断的,当然用户可以实现并注册满足自己业务逻辑的过滤器来控制扫描的结果,metadataReader 中有你过滤所需要的全部文件信息。至此,我们完成了文件的扫描过程的分析。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论