在Spring配置Mybatis的文件中我們可以看到如下代碼:
-
-
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
-
<property name="basePackage" value="org.tarena.note.dao">
-
</property>
MapperScannerConfigurer,讓它掃描特定的包,自動幫我們成批地創建映射器。這樣就大大減少了配置的工作量。
basePackage屬性是讓你爲映射器接口文件設置基本的包路徑。可以使用分號或逗號作爲分隔符設置多於一個的包路徑。每個映射器都會在指定的包路徑中遞歸地被搜索到。被發現的映射器將會使用spring對自動偵測組件默認的命名策略來命名。也就是說,如果沒有發現註解,它就會使用映射器的非大寫的非完全限定類名。如果發現了@Component或JSR-330@Named註解,它會獲取名稱。
通過上面的配置,Spring就會幫助我們對test.mybatis.dao下面所有接口進行自動的注入,而不需要爲每個接口重複在Spring配置文件中進行聲明瞭。那麼這個功能如何做到的呢?MapperScanner Configurer中又有哪些核心操作呢?同樣首先看下這個類實現了InitializingBean接口。馬上查找afterPropertiesSet方法來看看類的初始化邏輯。
-
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
但沒有任何有意義的實現,我們看到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, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
-
}
正是這裏。大致看下代碼實現,正是完成了對指定路徑掃描的邏輯。那麼,我們就以此爲入口,詳細地分析MapperScannerConfigurer所提供的邏輯實現
1.processPropertyPlaceHolders屬性的處理
首先,難題就是processPropertyPlaceHolders屬性的處理。或許許多人並未接觸過此屬性。我們只能查看processPropertyPlaceHolders()函數來反推此屬性所代表的功能。
-
private void processPropertyPlaceHolders() {
-
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
-
-
if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {
-
BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext)
-
.getBeanFactory().getBeanDefinition(beanName);
-
-
-
-
-
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的調用,這就意味着PropertiesResourceConfigurers還沒有被加載所有對於屬性文件的引用將會失效,爲避免此種情況發生,此方法手動地找出定義的PropertyResourceConfigurers並進行調用以以保證對於屬性的引用可以正常工作。
下面我們舉個例子說明:
如果在配置文件中添加如下代碼 :
-
<pre name="code" class="java"><bean class="org.mybatis.Spring.mapper.MapperScannerConfigurer">
-
<property name="basePackage" value="${basePackage}"/>
-
</bean>
此時你會發現這個配置並沒有生效。因爲在解析MapperScannerConfigurer這個bean的時候,配置文件還沒有被加載,爲了解決這個問題,Spring提供了processPropertyPlaceHloder屬性,你需要這樣配置MapperScannerConfigurer類型的bean:
-
<pre name="code" class="java"><bean class="org.mybatis.Spring.mapper.MapperScannerConfigurer">
-
<property name="basePackage" value="${basePackage}"/>
-
<property name="processPropertyPlaceHolders" value="true">
-
</bean>
通過processPropertyPlaceHolders屬性的配置,將程序引入我們正在分析的processPropertyPlaceHolders函數中來完成屬性文件的加載。至此,我們理清了這個函數的屬性,再回顧看下這個函數做的事情:
(1)找到所有已經註冊的PropertyResourceConfigurer類型的bean。
(2)模擬Spring的環境來用處理器。這裏通過使用呢new DefaultlistableBeanFactory()來模擬Spring中的環境(完成處理器的調用後便失效),將映射的bean,也就是MapperScannerConfigurer類型bean註冊到環境中來進行後處理器的調用。
2.根據配置屬性生成過濾器
在postProcessBeanDefinitionRegistry方法中可以看到,配置中支持很多屬性的設定,但是我們感興趣的或者說影響掃描結果的並不多,屬性設置後通過在scanner.registerFilters()代碼中生成對應的過濾器來控制掃描結果。
-
public void registerFilters() {
-
boolean acceptAllInterfaces = true;
-
-
-
-
-
-
-
-
-
if (this.annotationClass != null) {
-
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
-
acceptAllInterfaces = false;
-
}
-
-
-
-
if (this.markerInterface != null) {
-
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
-
@Override
-
protected boolean matchClassName(String className) {
-
return false;
-
}
-
});
-
acceptAllInterfaces = false;
-
}
-
-
-
-
-
-
if (acceptAllInterfaces) {
-
-
addIncludeFilter(new TypeFilter() {
-
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
-
return true;
-
}
-
});
-
}
-
-
-
-
addExcludeFilter(new TypeFilter() {
-
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
-
String className = metadataReader.getClassMetadata().getClassName();
-
return className.endsWith("package-info");
-
}
-
});
-
}
從上面的函數我們可以看出,控制掃描文件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類的父類ClassPathBeanDefinitionScanner的scan方法完成的。
-
public int scan(String... basePackages) {
-
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
-
-
doScan(basePackages);
-
-
-
if (this.includeAnnotationConfig) {
-
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
-
}
-
-
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
-
}
scan是個全局方法,掃描工作通過
委託給了doScan方法,同時,還包括了includeAnnotationConfig屬性的處理,AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);代碼主要是完成對於註解處理器的簡單註冊,我們下面主要分析下掃描功能的實現 。
-
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");
-
}
-
-
-
-
-
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);
-
}
-
}
-
}
-
-
return beanDefinitions;
-
}
此時,雖然還沒有完成介紹到掃描的過程,但是我們也應該理解了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<BeanDefinitionHolder>();
-
for (String basePackage : basePackages) {
-
-
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);
-
}
-
-
if (checkCandidate(beanName, candidate)) {
-
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
-
-
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(packageSearchPath);
-
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 ScannedGenericBeanDefinition(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信息。這裏,我們更感興趣的是isCandidateCompanent(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;
-
}
-