Spring 中 @Component、@Service 等註解如何被解析?

前言

@Component和@Service都是工作中常用的註解,Spring如何解析?

1.@Component解析流程

找入口

Spring Framework2.0開始,引入可擴展的XML編程機制,該機制要求XML Schema命名空間需要與Handler建立映射關係。

該關係配置在相對於classpath下的/META-INF/spring.handlers中。

Spring 中 @Component、@Service 等註解如何被解析?

如上圖所示 ContextNamespaceHandler對應context:... 分析的入口。

找核心方法

瀏覽ContextNamespaceHandler

Spring 中 @Component、@Service 等註解如何被解析?

在parse中有一個很重要的註釋

// Actually scan for bean definitions and register them.

ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);

大意是:
ClassPathBeanDefinitionScanner#doScan是掃描BeanDefinition並註冊的實現。


ClassPathBeanDefinitionScanner 的源碼如下:

<code>protected 

Set

 doScan(

String

... basePackages) {    Assert.notEmpty(basePackages, 

"At least one base package must be specified"

);    

Set

 beanDefinitions = 

new

 LinkedHashSet<>();    

for

 (

String

 basePackage : basePackages) {              

Set

 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; }/<code>

上邊的代碼,從方法名,猜測:

findCandidateComponents:從classPath掃描組件,並轉換為備選BeanDefinition,也就是要做的解析@Component的核心方法。

概要分析

findCandidateComponents在其父類
ClassPathScanningCandidateComponentProvider 中。

<code>

public

 

class

 

ClassPathScanningCandidateComponentProvider

 

implements

 

EnvironmentCapable

ResourceLoaderAware {

public

 Set findCandidateComponents(String basePackage) {    

if

 (

this

.componentsIndex != 

null

 && indexSupportsIncludeFilters()) {       

return

 addCandidateComponentsFromIndex(

this

.componentsIndex, basePackage);    }    

else

 {       

return

 scanCandidateComponents(basePackage);    } }

private

 Set scanCandidateComponents(String basePackage) {    Set candidates = new LinkedHashSet<>();    

try

 {       String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +             resolveBasePackage(basePackage) + 

'/'

 + 

this

.resourcePattern;       Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);                

for

 (Resource resource : resources) {                   

if

 (resource.isReadable()) {             

try

 {                MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);                

if

 (isCandidateComponent(metadataReader)) {                   ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);                   sbd.setSource(resource);                   

if

 (isCandidateComponent(sbd)) {                      candidates.add(sbd);                        }    }    

catch

 (IOException ex) {    

return

 candidates; } }/<code>

findCandidateComponents大體思路如下:

  1. String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX resolveBasePackage(basePackage) + '/' + this.resourcePattern; 將package轉化為ClassLoader類資源搜索路徑packageSearchPath,例如:com.wl.spring.boot轉化為classpath*:com/wl/spring/boot/**/*.class
  2. Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 加載搜索路徑下的資源。
  3. isCandidateComponent 判斷是否是備選組件
  4. candidates.add(sbd); 添加到返回結果的list


ClassPathScanningCandidateComponentProvider#isCandidateComponent其源碼如下:

<code>

protected

 

boolean

 

isCandidateComponent

(MetadataReader metadataReader)

 

throws

 IOException 

{         

for

 (TypeFilter tf : 

this

.includeFilters) {       

if

 (tf.match(metadataReader, getMetadataReaderFactory())) {          

return

 isConditionMatch(metadataReader);       }    }    

return

 

false

; }/<code>

includeFilters由registerDefaultFilters()設置初始值,有@Component,沒有@Service啊?

<code>

protected

 

void

 

registerDefaultFilters

()

 

{    

this

.includeFilters.add(

new

 AnnotationTypeFilter(Component

.

class

))

;    ClassLoader cl = ClassPathScanningCandidateComponentProvider

.

class

.

getClassLoader

()

;    

try

 {       

this

.includeFilters.add(

new

 AnnotationTypeFilter(             ((Class extends Annotation>) ClassUtils.forName(

"javax.annotation.ManagedBean"

, cl)), 

false

));       logger.trace(

"JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"

);    }    

catch

 (ClassNotFoundException ex) {           }    

try

 {       

this

.includeFilters.add(

new

 AnnotationTypeFilter(             ((Class extends Annotation>) ClassUtils.forName(

"javax.inject.Named"

, cl)), 

false

));       logger.trace(

"JSR-330 'javax.inject.Named' annotation found and supported for component scanning"

);    }    

catch

 (ClassNotFoundException ex) {           } }/<code>

Spring如何處理@Service的註解的呢????

2.查文檔找思路

查閱官方文檔,下面這話:

https://docs.spring.io/spring/docs/5.0.17.RELEASE/spring-framework-reference/core.html#beans-meta-annotations

@Component is a generic stereotype for any Spring-managed component. @Repository, @Service, and @Controller are specializations of @Component

大意如下:

@Component是任何Spring管理的組件的通用原型。@Repository、@Service和@Controller是派生自@Component。

<code>

@Target

({ElementType.TYPE})

@Retention

(RetentionPolicy.RUNTIME)

@Documented

@Component

public 

@interface

 Service {        

@AliasFor

(annotation = Component.class)    String value() default 

""

; }/<code>

@Component是@Service的元註解,Spring 大概率,在讀取@Service,也讀取了它的元註解,並將@Service作為@Component處理。

3. 探尋@Component派生性流程

回顧
ClassPathScanningCandidateComponentProvider 中的關鍵的代碼片段如下:

<code>

private

 Set scanCandidateComponents(String basePackage) {    MetadataReader metadataReader                 =getMetadataReaderFactory().getMetadataReader(resource);      

if

(isCandidateComponent(metadataReader)){            }          }

public

 

final

 MetadataReaderFactory getMetadataReaderFactory() {    

if

 (

this

.metadataReaderFactory == 

null

) {       

this

.metadataReaderFactory = new CachingMetadataReaderFactory();    }    

return

 

this

.metadataReaderFactory; }/<code>

1. 確定metadataReader


CachingMetadataReaderFactory繼承自
SimpleMetadataReaderFactory,就是對
SimpleMetadataReaderFactory加了一層緩存。

其內部的
SimpleMetadataReaderFactory#getMetadataReader 問:

<code>

public

 

class

 

SimpleMetadataReaderFactory

 

implements

 

MetadataReaderFactory

 

{     

public

 MetadataReader 

getMetadataReader

(Resource resource)

 

throws

 IOException 

{    

return

 

new

 SimpleMetadataReader(resource, 

this

.resourceLoader.getClassLoader()); }     }/<code>

這裡可以看出

MetadataReader metadataReader =new SimpleMetadataReader(...);

2.查看match方法找重點方法

Spring 中 @Component、@Service 等註解如何被解析?

AnnotationTypeFilter#matchself方法如下:

<code> 

protected

 boolean matchSelf(MetadataReader metadataReader) {    AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();    

return

 metadata.hasAnnotation(

this

.annotationType.getName()) ||          (

this

.considerMetaAnnotations && metadata.hasMetaAnnotation(

this

.annotationType.getName())); }/<code>


metadata.hasMetaAnnotation法,從名稱看是處理元註解,我們重點關注

逐步分析

找metadata.hasMetaAnnotation

metadata=metadataReader.getAnnotationMetadata();

metadataReader =new SimpleMetadataReader(...)

metadata= new SimpleMetadataReader#getAnnotationMetadata()

<code> 
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
   InputStream 

is

 = 

new

 BufferedInputStream(resource.getInputStream());    ClassReader classReader;    

try

 {       classReader = 

new

 ClassReader(

is

);    }    

catch

 (IllegalArgumentException ex) {       

throw

 

new

 NestedIOException(

"ASM ClassReader failed to parse class file - "

 +             

"probably due to a new Java class file version that isn't supported yet: "

 + resource, ex);    }    

finally

 {       

is

.close();    }    AnnotationMetadataReadingVisitor visitor =             

new

 AnnotationMetadataReadingVisitor(classLoader);    classReader.accept(visitor, ClassReader.SKIP_DEBUG);    

this

.annotationMetadata = visitor;        

this

.classMetadata = visitor;    

this

.resource = resource; }/<code>

metadata=new SimpleMetadataReader(...)**.**getAnnotationMetadata()= new
AnnotationMetadataReadingVisitor(。。)

也就是說

metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation

其方法如下:

<code>

public

 

class

 

AnnotationMetadataReadingVisitor

{     

public

 boolean hasMetaAnnotation(String metaAnnotationType) {    Collection> allMetaTypes = 

this

.metaAnnotationMap.values();    

for

 (Set metaTypes : allMetaTypes) {       

if

 (metaTypes.contains(metaAnnotationType)) {          

return

 

true

;       }    }    

return

 

false

; } }/<code>

邏輯很簡單,就是判斷該註解的元註解在,在不在metaAnnotationMap中,如果在就返回true。

這裡面核心就是metaAnnotationMap,搜索
AnnotationMetadataReadingVisitor類,沒有發現賦值的地方??!。

查找metaAnnotationMap賦值

回到SimpleMetadataReader 的方法,

<code> 
SimpleMetadataReader(Resource resource,   ClassLoader classLoader) 

throws

 IOException { AnnotationMetadataReadingVisitor visitor = 

new

 AnnotationMetadataReadingVisitor(classLoader); classReader.accept(visitor, ClassReader.SKIP_DEBUG);  

this

.annotationMetadata = visitor;  } /<code>

發現一個可疑的語句:classReader.accept。

查看accept方法

<code>

public

 

class

 

ClassReader

 {

        

public

 

void

 

accept

(..省略代碼)

{          readElementValues(     classVisitor.visitAnnotation(annotationDescriptor,   

true

),     currentAnnotationOffset,      

true

,     charBuffer); } }/<code>

查看readElementValues方法

<code>

public

 

class

 

ClassReader

{     

private

 

int

 

readElementValues

(     

final

 AnnotationVisitor annotationVisitor,     

final

 

int

 annotationOffset,     

final

 

boolean

 named,     

final

 

char

[] charBuffer)

 

{   

int

 currentOffset = annotationOffset;      

int

 numElementValuePairs = readUnsignedShort(currentOffset);   currentOffset += 

2

;   

if

 (named) {          

while

 (numElementValuePairs-- > 

0

) {       String elementName = readUTF8(currentOffset, charBuffer);       currentOffset =           readElementValue(annotationVisitor, currentOffset + 

2

, elementName, charBuffer);     }   } 

else

 {          

while

 (numElementValuePairs-- > 

0

) {       currentOffset =           readElementValue(annotationVisitor, currentOffset,   

null

, charBuffer);     }   }   

if

 (annotationVisitor != 

null

) {     annotationVisitor.visitEnd();   }   

return

 currentOffset; } }/<code>

這裡面的核心就是
annotationVisitor.visitEnd();

確定annotationVisitor

這裡的annotationVisitor=
AnnotationMetadataReadingVisitor#visitAnnotation

源碼如下,注意這裡傳遞了metaAnnotationMap!!

<code>

public

 

class

 

AnnotationMetadataReadingVisitor

{

public

 AnnotationVisitor visitAnnotation(String desc, boolean visible) {    String className = Type.getType(desc).getClassName();    

this

.annotationSet.add(className);    

return

 new AnnotationAttributesReadingVisitor(          className, 

this

.attributesMap,               

this

.metaAnnotationMap, 

this

.classLoader); } }/<code>

annotationVisitor=AnnotationAttributesReadingVisitor

查閱annotationVisitor.visitEnd()

annotationVisitor=AnnotationAttributesReadingVisitor#visitEnd()

<code>

public

 

class

 

AnnotationAttributesReadingVisitor

{

public

 void visitEnd() {    

super

.visitEnd();    Class extends Annotation> annotationClass = 

this

.attributes.annotationType();    

if

 (annotationClass != 

null

) {       List attributeList = 

this

.attributesMap.

get

(

this

.annotationType);       

if

 (attributeList == 

null

) {          

this

.attributesMap.add(

this

.annotationType, 

this

.attributes);       }       

else

 {          attributeList.add(

0

this

.attributes);       }       

if

 (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationClass.getName())) {          

try

 {             Annotation[] metaAnnotations = annotationClass.getAnnotations();             

if

 (!ObjectUtils.isEmpty(metaAnnotations)) {                Set visited = new LinkedHashSet<>();                

for

 (Annotation metaAnnotation : metaAnnotations) {                   recursivelyCollectMetaAnnotations(visited, metaAnnotation);                }                

if

 (!visited.isEmpty()) {                   Set metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());                   

for

 (Annotation ann : visited) {                      metaAnnotationTypeNames.add(ann.annotationType().getName());                   }                   

this

.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);                }             }          }          

catch

 (Throwable ex) {             

if

 (logger.isDebugEnabled()) {                logger.debug(

"Failed to introspect meta-annotations on "

 + annotationClass + 

": "

 + ex);             }          }       }    } } }/<code>

內部方法
recursivelyCollectMetaAnnotations 遞歸的讀取註解,與註解的元註解(讀@Service,再讀元註解@Component),並設置到metaAnnotationMap,也就是
AnnotationMetadataReadingVisitor 中的metaAnnotationMap中。

總結

大致如下:

ClassPathScanningCandidateComponentProvider#findCandidateComponents

  1. 將package轉化為ClassLoader類資源搜索路徑packageSearchPath
  2. 加載搜索路徑下的資源。
  3. isCandidateComponent 判斷是否是備選組件。

內部調用的TypeFilter的match方法:

AnnotationTypeFilter#matchself中
metadata.hasMetaAnnotation處理元註解

metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation

就是判斷當前註解的元註解在不在metaAnnotationMap中。


AnnotationAttributesReadingVisitor#visitEnd()內部方法
recursivelyCollectMetaAnnotations 遞歸的讀取註解,與註解的元註解(讀@Service,再讀元註解@Component),並設置到metaAnnotationMap

  1. 添加到返回結果的list


分享到:


相關文章: