速读摘要文章源自JAVA秀-https://www.javaxiu.com/3727.html
在读取@Service,也读取了它的元注解,并将@Service作为@Component处理。}逻辑很简单,就是判断该注解的元注解在,在不在metaAnnotationMap中,如果在就返回true。文章源自JAVA秀-https://www.javaxiu.com/3727.html
原文约 2257 字 | 图片 9 张 | 建议阅读 5 分钟 | 评价反馈文章源自JAVA秀-https://www.javaxiu.com/3727.html
京东一面:说说@Component,@Service等注解是如何被解析的?
点击关注 ? Java面试那些事儿 文章源自JAVA秀-https://www.javaxiu.com/3727.html
点击关注下方公众号,Java面试资料 都在这里文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
作者:温安适文章源自JAVA秀-https://www.javaxiu.com/3727.html
来源:https://juejin.im/post/5ef5710cf265da22ce391aba文章源自JAVA秀-https://www.javaxiu.com/3727.html
正文如下:文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
# 前言文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
@Component和@Service都是工作中常用的注解,Spring如何解析?文章源自JAVA秀-https://www.javaxiu.com/3727.html
# @Component解析流程文章源自JAVA秀-https://www.javaxiu.com/3727.html
找入口文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
Spring Framework2.0开始,引入可扩展的XML编程机制,该机制要求XML Schema命名空间需要与Handler建立映射关系。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
该关系配置在相对于classpath下的/META-INF/spring.handlers中。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
如上图所示 ContextNamespaceHandler对应<context:...> 分析的入口。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
找核心方法文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
浏览ContextNamespaceHandler 文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
在parse中有一个很重要的注释文章源自JAVA秀-https://www.javaxiu.com/3727.html
// Actually scan for bean definitions and register them.ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);文章源自JAVA秀-https://www.javaxiu.com/3727.html
大意是:ClassPathBeanDefinitionScanner#doScan是扫描BeanDefinition并注册的实现 。ClassPathBeanDefinitionScanner 的源码如下:文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlprotected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
//findCandidateComponents 读资源装换为BeanDefinition
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;
}
上边的代码,从方法名,猜测:文章源自JAVA秀-https://www.javaxiu.com/3727.html
findCandidateComponents:从classPath扫描组件,并转换为备选BeanDefinition,也就是要做的解析@Component的核心方法。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
概要分析文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
findCandidateComponents在其父类ClassPathScanningCandidateComponentProvider 中。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlpublic class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
//省略其他代码
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> 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;
}
}
文章源自JAVA秀-https://www.javaxiu.com/3727.html
findCandidateComponents大体思路如下:文章源自JAVA秀-https://www.javaxiu.com/3727.html
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文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
2.Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 加载搜素路径下的资源。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
3.isCandidateComponent 判断是否是备选组件文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
4.candidates.add(sbd); 添加到返回结果的list文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
ClassPathScanningCandidateComponentProvider#isCandidateComponent其源码如下:文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlprotected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
//省略部分代码
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
includeFilters由registerDefaultFilters()设置初始值,有@Component,没有@Service啊?文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlprotected 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) {
// 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.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
Spring如何处理@Service的注解的呢????文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
# 查文档找思路文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
查阅官方文档,下面这话:文章源自JAVA秀-https://www.javaxiu.com/3727.html
https://docs.spring.io/spring/docs/5.0.17.RELEASE/spring-framework-reference/core.html#beans-meta-annotations文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
@Component is a generic stereotype for any Spring-managed component. @Repository, @Service, and @Controller are specializations of @Component文章源自JAVA秀-https://www.javaxiu.com/3727.html
大意如下:文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
@Component是任何Spring管理的组件的通用原型。@Repository、@Service和@Controller是派生自@Component。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// @Service 派生自@Component
@Component
public @interface Service {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Component是@Service的元注解,Spring 大概率,在读取@Service,也读取了它的元注解,并将@Service作为@Component处理。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
# 探寻@Component派生性流程文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
回顾ClassPathScanningCandidateComponentProvider 中的关键的代码片段如下:文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlprivate Set<BeanDefinition> 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;
}
1. 确定metadataReader文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
CachingMetadataReaderFactory继承自 SimpleMetadataReaderFactory,就是对SimpleMetadataReaderFactory加了一层缓存。文章源自JAVA秀-https://www.javaxiu.com/3727.html
其内部的SimpleMetadataReaderFactory#getMetadataReader 为:文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlpublic class SimpleMetadataReaderFactory implements MetadataReaderFactory {
@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
}
}
这里可以看出文章源自JAVA秀-https://www.javaxiu.com/3727.html
MetadataReader metadataReader =new SimpleMetadataReader(...);文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
2.查看match方法找重点方法文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
AnnotationTypeFilter#matchself方法如下:文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html@Override
protected boolean matchSelf(MetadataReader metadataReader) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
是metadata.hasMetaAnnotation法,从名称看是处理元注解,我们重点关注文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
逐步分析文章源自JAVA秀-https://www.javaxiu.com/3727.html
找metadata.hasMetaAnnotation文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
metadata=metadataReader.getAnnotationMetadata();文章源自JAVA秀-https://www.javaxiu.com/3727.html
metadataReader =new SimpleMetadataReader(...)文章源自JAVA秀-https://www.javaxiu.com/3727.html
metadata= new SimpleMetadataReader#getAnnotationMetadata()文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html//SimpleMetadataReader 的构造方法
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;
// (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)
this.classMetadata = visitor;
this.resource = resource;
}
文章源自JAVA秀-https://www.javaxiu.com/3727.html
metadata=new SimpleMetadataReader(...).getAnnotationMetadata()= new AnnotationMetadataReadingVisitor(。。)文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
也就是说文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
其方法如下:文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlpublic class AnnotationMetadataReadingVisitor{
// 省略部分代码
@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;
}
}
逻辑很简单,就是判断该注解的元注解在,在不在metaAnnotationMap中,如果在就返回true。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
这里面核心就是metaAnnotationMap,搜索AnnotationMetadataReadingVisitor类,没有发现赋值的地方??!。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
查找metaAnnotationMap赋值文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
回到SimpleMetadataReader 的方法,文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html//这个accept方法,很可疑,在赋值之前执行
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
//省略其他代码
AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
classReader.accept(visitor, ClassReader.SKIP_DEBUG);
this.annotationMetadata = visitor;
}
发现一个可疑的语句:classReader.accept。文章源自JAVA秀-https://www.javaxiu.com/3727.html
查看accept方法文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlpublic class ClassReader {
//省略其他代码
public void accept(..省略代码){
//省略其他代码
readElementValues(
classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
currentAnnotationOffset,
true,
charBuffer);
}
}
文章源自JAVA秀-https://www.javaxiu.com/3727.html
查看readElementValues方法文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlpublic class ClassReader{
//省略其他代码
private int readElementValues(
final AnnotationVisitor annotationVisitor,
final int annotationOffset,
final boolean named,
final char[] charBuffer) {
int currentOffset = annotationOffset;
// Read the num_element_value_pairs field (or num_values field for an array_value).
int numElementValuePairs = readUnsignedShort(currentOffset);
currentOffset += 2;
if (named) {
// Parse the element_value_pairs array.
while (numElementValuePairs-- > 0) {
String elementName = readUTF8(currentOffset, charBuffer);
currentOffset =
readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer);
}
} else {
// Parse the array_value array.
while (numElementValuePairs-- > 0) {
currentOffset =
readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer);
}
}
if (annotationVisitor != null) {
annotationVisitor.visitEnd();
}
return currentOffset;
}
}
这里面的核心就是 annotationVisitor.visitEnd();文章源自JAVA秀-https://www.javaxiu.com/3727.html
确定annotationVisitor文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
这里annotationVisitor=AnnotationMetadataReadingVisitor#visitAnnotation源码如下,注意这里传递了metaAnnotationMap!!文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlpublic class AnnotationMetadataReadingVisitor{
@Override
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);
}
}
annotationVisitor=AnnotationAttributesReadingVisitor文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
查阅annotationVisitor.visitEnd()文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
annotationVisitor=AnnotationAttributesReadingVisitor#visitEnd()文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.htmlpublicclassAnnotationAttributesReadingVisitor{
@Override
public void visitEnd() {
super.visitEnd();
Class<? extends Annotation> annotationClass = this.attributes.annotationType();
if (annotationClass != null) {
List<AnnotationAttributes> 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<Annotation> visited = new LinkedHashSet<>();
for (Annotation metaAnnotation : metaAnnotations) {
recursivelyCollectMetaAnnotations(visited, metaAnnotation);
}
if (!visited.isEmpty()) {
Set<String> 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);
}
}
}
}
}
}
内部方法recursivelyCollectMetaAnnotations 递归的读取注解,与注解的元注解(读@Service,再读元注解@Component),并设置到metaAnnotationMap,也就是AnnotationMetadataReadingVisitor 中的metaAnnotationMap中。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
# 总结文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
大致如下:文章源自JAVA秀-https://www.javaxiu.com/3727.html
ClassPathScanningCandidateComponentProvider#findCandidateComponents文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
1. 将package转化为ClassLoader类资源搜索路径packageSearchPath文章源自JAVA秀-https://www.javaxiu.com/3727.html
2. 加载搜素路径下的资源。文章源自JAVA秀-https://www.javaxiu.com/3727.html
3. isCandidateComponent 判断是否是备选组件。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
内部调用的TypeFilter的match方法:文章源自JAVA秀-https://www.javaxiu.com/3727.html
AnnotationTypeFilter#matchself中metadata.hasMetaAnnotation处理元注解 metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation就是判断当前注解的元注解在不在metaAnnotationMap中。文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
AnnotationAttributesReadingVisitor#visitEnd()内部方法recursivelyCollectMetaAnnotations 递归的读取注解,与注解的元注解(读@Service,再读元注解@Component),并设置到metaAnnotationMap文章源自JAVA秀-https://www.javaxiu.com/3727.html
文章源自JAVA秀-https://www.javaxiu.com/3727.html
4. 添加到返回结果的list文章源自JAVA秀-https://www.javaxiu.com/3727.html
热门推荐:@所有人 你的手机号码有风险!TIOBE 公布 3 月榜单,Java 还是第一吗?唯品会一面:Spring 为何需要三级缓存解决循环依赖,而不是二级缓存?文章源自JAVA秀-https://www.javaxiu.com/3727.html

评论