前言
先做個基礎知識普及,如何理解註解?
我的理解,註解就是擴展版的接口,接口的使用場景很有限,只能由類去實現接口,而註解則豐富的多,它可以用在類,實例屬性,方法,參數等上面;
我對接口的理解是:接口的核心是用來表示一類事物的,比如:Person接口,凡是繼承Person接口的都是人,Driver接口,凡是繼承這個接口的都是司機。這種設計的作用就是業務邏輯(框架代碼)可用通過類的這種繼承關係來找到某一類對象。
註解大致延續和豐富了接口的這種先天的作用,一般情況下,註解被用作:標註一類事物(同接口一樣);擴展一種功能;
1,標註一類事務
很好理解,比如Spring Bean,當一個類被@Component,@Service,@Repository等修飾的時候,它就表示是一個Spring Bean,在初始化的時候就會被Spring容器管理;
2,擴展一種功能
通常框架代碼通過動態代理的形式增強被標註的類或者方法,比如:@Transactional,@Aspect,@Pointcut等;
原理
上面說的是註解的作用,但是註解是如何起作用的?這就好比定一個標準,得有人去執行標準,而執行標準的就是框架的源碼,也就是本篇要研究的,標準定義被Service修飾就是一個Bean,框架代碼在處理的時候當遇到類被Service註解修飾,就會把類封裝成BeanDefinition進行註冊;其他人隨便寫一個註解@MyService,加到類頭上,Spring就不會把它當成Bean來管理。
配置
spirng配置文件,配置掃描路徑:
<!-- 開啓註解掃描 -->
<context:component-scan base-package="org.liuwei"></context:component-scan>
這麼一行配置,背後當然是有對應處理代碼,代碼就在命名空間句柄類中初始化
META-INF/spring.handler ——句柄配置文件,會把對應關係維護起來,在解析配置文件的時候方便找到對應的解析類
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
解析與掃描
Spring處理文件,通常是由解析類和掃描類組成,或者叫解析器和掃描器,解析器中獲取掃描器,由掃描器負責掃描配置文件,得到數據後,解析器再做後續處理。
從上面句柄類可以看到 component-scan 對應的處理類:ComponentScanBeanDefinitionParser,處理註解Bean就是這樣的套路,下面解析代碼中先得到一個掃描器,調用掃描器doScan方法獲取BeanDefinitionHolder集合,掃描器能夠掃描出哪些是Bean,其中必定有掃描的過濾規則,可用猜想到規則就是那些註解(接口)。跟代碼發現在構造掃描器對象的時候,就會把過濾規則生成。
org.springframework.context.annotation.ComponentScanBeanDefinitionParser
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 這個方法雖然返回類型是BeanDefinition,但最終返回的是null,
// 因爲掃描路徑下肯定不止一個bean,即便要返回也應該是返回集合纔對。
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
// 拆分配置的多個路徑
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
// 這裏獲取beanDefinitions集合,那麼判斷邏輯肯定也在這個裏面,我們重點看這裏
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
// 註冊BeanDefinition到BeanFactory
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
掃描器,在器構造方法中調用父類的registerDefaultFilters方法初始化過濾規則。
org.springframework.context.annotation.ClassPathBeanDefinitionScanner
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) {
// 找一個目錄下的所有BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 循環這些BeanDefinition
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);
// 把BeanDefinition註冊進registry(這個registry就是BeanFactory)
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
下面來看看匹配邏輯,就是:爲什麼只有常用的那幾個註解纔會被解析成Bean?自定義的不行
掃描器父類:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
父類的 註冊默認過濾規則 方法
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
// 上來就把Component註解加進去
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
// 後面兩個分別是tomcat和標準java的兩個註解
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
這裏提一下includeFilters 和 excludeFilters
includeFilters是配合use-default-filters一起使用,只有use-default-filters=false才行,下面配置表示Spring只掃描用Controller註解修飾的類
<context:component-scan base-package="org.liuwei.smart.service" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
excludeFilters在use-default-filters=true或者不配置use-default-filters時可用,下面配置表示Spring不會管理用Controller註解修飾的類
<context:component-scan base-package="org.liuwei.smart.service">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
說完這個,其實已經解釋了爲什麼自定義的不行,因爲還要配置才行,比如:自定義一個特殊的目錄,目錄下的class只有用自定義的註解纔會被Spring管理
<context:component-scan base-package="org.liuwei.smart.service" use-default-filters="false">
<context:include-filter type="annotation" expression="org.liuwei.annotation.MyService"/>
</context:component-scan>
繼續看查詢Bean的邏輯,依然是父類提供的方法,方法很簡單:
1,遍歷目錄下所有的資源(class),
2,逐一判斷是否符合Bean條件;
// 查詢候選的組件bean
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
// 要掃描的路徑
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 獲取全部的Class
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;
}
校驗是否符合Bean的條件,邏輯match方法中
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
// excludeFilters剔除哪些註解,通常不會配置
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
// includeFilters包含的註解,通常也不配置,但是有默認值
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return isConditionMatch(metadataReader);
}
}
return false;
}
調用TypeFilter的match方法,上面已經分析了includeFilters裏面放的TypeFilter是封裝了Component.class。
// 上來就把Component註解加進去
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter
過濾器父類提供的匹配模板方法 ,具體的匹配規則在子類中實現
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// This method optimizes avoiding unnecessary creation of ClassReaders
// as well as visiting over those readers.
if (matchSelf(metadataReader)) {
return true;
}
ClassMetadata metadata = metadataReader.getClassMetadata();
if (matchClassName(metadata.getClassName())) {
return true;
}
// 略去部分代碼
return false;
}
過濾器子類:org.springframework.core.type.filter.AnnotationTypeFilter
@Override
protected boolean matchSelf(MetadataReader metadataReader) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
// 這裏面兩個判斷邏輯
// metadata.hasAnnotation 是否被@Component註釋
// metadata.hasMetaAnnotation 是否被@Component註釋的子註解註釋
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor
這是資源的註解元數據(可能的Bean對象的元數據),它有兩個成員變量:
- Set<String> annotationSet :類的頭上加了哪些註解,你可能給一個類加n個註解,都會記錄在這個集合裏面,在加載類的時候就會初始化;
- Map<String, Set<String>> metaAnnotationMap :key=註解,value=父註解 (測試結果如此,沒有詳細考究)
具體的判斷邏輯則是先從Set集合中找,沒有的話再到Map中找,找到了則返回true,表示是一個Bean。
例如:用Service標註
Set集合記錄:[org.springframework.stereotype.Service]
Map集合記錄:{org.springframework.stereotype.Service=[org.springframework.stereotype.Component]}
但是includeFilters集合中的數據是Component而不是Service,所以通過集合校驗不成立,再通過Map校驗,邏輯見下面代碼片段,方法:hasMetaAnnotation,拿value中的數據與includeFilters比對,這樣就匹配成功了。
至此,把Bean封裝成BeanDefinitionHolder數組返回給處理器。
protected final Set<String> annotationSet = new LinkedHashSet<String>(4);
protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4);
public boolean hasAnnotation(String annotationName) {
// 集合操作
return this.annotationSet.contains(annotationName);
}
@Override
public boolean hasMetaAnnotation(String metaAnnotationType) {
Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values();
for (Set<String> metaTypes : allMetaTypes) {
if (metaTypes.contains(metaAnnotationType)) {
return true;
}
}
return false;
}