spring启动流程(二):包的扫描流程
创始人
2024-01-20 04:17:10

在applicationContext的创建中,我们分析了applicationContext的创建过程,在本文中,我们将分析spring是如何进行包扫描的。

依旧是AnnotationConfigApplicationContext的构造方法:

public AnnotationConfigApplicationContext(String... basePackages) {this();//对传入的包进行扫描,扫描完成后,会得到一个 BeanDefinition 的集合scan(basePackages);refresh();
}
复制代码

这次我们将目光放在scan(basePackages);上,进入该方法:

AnnotationConfigApplicationContext#scan

public void scan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");// 这里的scanner对象就是在this()中创建的this.scanner.scan(basePackages);
}
复制代码

这个方法关键代码是this.scanner.scan(basePackages);,这个scanner就是在this()中创建的对象:

public AnnotationConfigApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);// scanner 就是在这里创建的this.scanner = new ClassPathBeanDefinitionScanner(this);
}
复制代码

继续追踪,这里我们对不重要的方法仅给出调用链,重点关注扫描包的过程:

AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)|-AnnotationConfigApplicationContext#scan|-ClassPathBeanDefinitionScanner#scan|-ClassPathBeanDefinitionScanner#doScan
复制代码

ClassPathBeanDefinitionScanner#doScan 代码如下:

protected Set doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set beanDefinitions = new LinkedHashSet<>();//遍历需要扫描的包路径for (String basePackage : basePackages) {//获取所有符合条件的BeanDefinitionSet candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {//绑定BeanDefinition与ScopeScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());//查看是否配置类是否指定bean的名称,如没指定则使用类名首字母小写String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);//下面两个if是处理lazy、Autowire、DependencyOn、initMethod、enforceInitMethod、destroyMethod、// enforceDestroyMethod、Primary、Role、Description这些逻辑的if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}//检查bean是否存在if (checkCandidate(beanName, candidate)) {//又包装了一层BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);//检查scope是否创建,如未创建则进行创建definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);//注册 beanDefinitionregisterBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;
}复制代码

这段代码完成的功能很明了,大体上做了以下几件事:

  1. 根据包路径,得到符合条件的 BeanDefinition
  2. 遍历 BeanDefinition,进一步丰富beanDefinition信息
  3. 将 BeanDefinition 添加到 beanFactory

BeanDefinition也是spring的重要组件之一,关于BeanDefinition的分析,可参考spring组件之BeanDefinition。

接下来我们主要分析这三个的操作。

1. 根据包路径得到 BeanDefinition

这一步主要发生在Set candidates = findCandidateComponents(basePackage);,我们跟进去看看代码的执行,这里依旧对不重要代码给出调用链,该方法的调用如下:

AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)|-AnnotationConfigApplicationContext#scan|-ClassPathBeanDefinitionScanner#scan|-ClassPathBeanDefinitionScanner#doScan|-ClassPathScanningCandidateComponentProvider#findCandidateComponents|-ClassPathScanningCandidateComponentProvider#scanCandidateComponents
复制代码

最终调用到了ClassPathScanningCandidateComponentProvider#scanCandidateComponents,代码如下(有删减):

private Set scanCandidateComponents(String basePackage) {Set candidates = new LinkedHashSet<>();//组装扫描路径(组装完成后是这种格式:classpath*:org/springframework/learn/demo01/**/*.class)String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;//根据路径获取资源对象,即扫描出该路径下的的所有class文件,得到 ResourceResource[] resources = getResourcePatternResolver().getResources(packageSearchPath);for (Resource resource : resources) {if (resource.isReadable()) {//根据资源对象获取资源对象的MetadataReaderMetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);// 这里做了两件事:// 1. 是否需要初始化为spring bean,即是否有 @Component、@Service等注解// 2. 查看配置类是否有@Conditional一系列的注解,然后是否满足注册Bean的条件if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setResource(resource);sbd.setSource(resource);if (isCandidateComponent(sbd)) {candidates.add(sbd);}}}}return candidates;
}
复制代码

可以看到,以上代码做了三件事:

  1. 根据传入的basePackage得到扫描路径
  2. 根据扫描路径得到该路径下的所有class文件对应的Resource
  3. 将 Resource 转化为 beanDefinition

接下来我们就以上代码进行分析。

1.1 根据basePackage得到包扫描路径

这一步没啥好分析,就是一个字符串的拼接与替换,将传入的org.springframework.learn.demo01转换为classpath*:org/springframework/learn/demo01/**/*.class,相关代码就一行:

String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;
复制代码

1.2 扫描包路径

得到包扫描路径后,接下来就是进行扫描了。spring在扫描时,会把扫描路径下的所有class文件扫描出来,然后封装成Resource,代码如下

Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
复制代码

跟进代码,同样地,我们对不重要的方法,依旧只给出方法调用:

AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)|-AnnotationConfigApplicationContext#scan|-ClassPathBeanDefinitionScanner#scan|-ClassPathBeanDefinitionScanner#doScan|-ClassPathScanningCandidateComponentProvider#findCandidateComponents|-ClassPathScanningCandidateComponentProvider#scanCandidateComponents|- GenericApplicationContext#getResources|-AbstractApplicationContext#getResources|-PathMatchingResourcePatternResolver#getResources|-PathMatchingResourcePatternResolver#findPathMatchingResources
复制代码

我们将代码聚集于PathMatchingResourcePatternResolver#findPathMatchingResources:

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {// 传入的 locationPattern 是 classpath*:org/springframework/learn/demo01/**/*.class// rootDirPath 是 classpath*:org/springframework/learn/demo01/String rootDirPath = determineRootDir(locationPattern);// subPattern 是 **/*.classString subPattern = locationPattern.substring(rootDirPath.length());// 这里返回的 Resource 是 rootDirPath 的绝对路径(用url表示)// URL [file:/xxx/spring-learn/build/classes/java/main/org/springframework/learn/demo01/]Resource[] rootDirResources = getResources(rootDirPath);Set result = new LinkedHashSet<>(16);for (Resource rootDirResource : rootDirResources) {rootDirResource = resolveRootDirResource(rootDirResource);URL rootDirUrl = rootDirResource.getURL();if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);if (resolvedUrl != null) {rootDirUrl = resolvedUrl;}rootDirResource = new UrlResource(rootDirUrl);}// 处理 vfs 资源查找if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));}// 处理jar包文件查找else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));}// 处理文件路径下的文件查找else {result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));}}return result.toArray(new Resource[0]);
}
复制代码

通过分析,发现该类的处理过程如下:

  1. 通过传入的 locationPattern 得到该pattern下的url绝对路径,封装为Resource
  2. 遍历返回的路径,查找class文件,封装为Resource

我们来看看spring是如何将pattrn转换为url路径的,我们跟进代码:

|-PathMatchingResourcePatternResolver#getResources|-PathMatchingResourcePatternResolver#findAllClassPathResources|-PathMatchingResourcePatternResolver#doFindAllClassPathResources
复制代码

最终代码到了PathMatchingResourcePatternResolver#doFindAllClassPathResources:

protected Set doFindAllClassPathResources(String path) throws IOException {Set result = new LinkedHashSet<>(16);ClassLoader cl = getClassLoader();// path对应的urlEnumeration resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));while (resourceUrls.hasMoreElements()) {URL url = resourceUrls.nextElement();// 将url转换为Resource,并添加到结果中result.add(convertClassLoaderURL(url));}if ("".equals(path)) {addAllClassLoaderJarRoots(cl, result);}return result;
}// 将url转换为Resource
protected Resource convertClassLoaderURL(URL url) {return new UrlResource(url);
}
复制代码

此时传入的pathorg/springframework/learn/demo01/,从代码可知,最终调用了java的ClassLoader方法来获取path对应的url,然后将url转换为Resource添加到结果集中并返回。

拿到类的绝对路径之后,接下就是对路径进行遍历,拿到class文件了。让我们再回到PathMatchingResourcePatternResolver#findPathMatchingResources,spring扫描时,会根据传入的url类型,共扫描3个地方:

  1. vfs
  2. jar包
  3. 文件路径

vfs注释上说是"URL protocol for a general JBoss VFS resource",即通用JBoss VFS资源的URL协议,这里不深究。如果项目中引入了jar包且需要扫描jar中的路径,就会使用jar包扫描方式进行class文件查找,由于调试时,demo01是使用文件方式扫描的,这里就重点分析文件扫描方式,至于jar是如何扫描的,有兴趣的小伙伴可自行研究下。

我们跟进findPathMatchingResources方法:

|-PathMatchingResourcePatternResolver#findPathMatchingResources|-PathMatchingResourcePatternResolver#doFindPathMatchingFileResources|-PathMatchingResourcePatternResolver#doFindMatchingFileSystemResources
复制代码
protected Set doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {// 这里进行文件查找Set matchingFiles = retrieveMatchingFiles(rootDir, subPattern);Set result = new LinkedHashSet<>(matchingFiles.size());for (File file : matchingFiles) {result.add(new FileSystemResource(file));}return result;
}
复制代码

PathMatchingResourcePatternResolver#doFindMatchingFileSystemResources中,spring将扫描到的File转换为FileSystemResource保存,这是我们遇到的第二个Resource类型了(前面为UrlResource,这里为FileSystemResource).

接下我们重点关注Set matchingFiles = retrieveMatchingFiles(rootDir, subPattern);,看看spring是如何完成文件查找的:

|-PathMatchingResourcePatternResolver#findPathMatchingResources|-PathMatchingResourcePatternResolver#doFindPathMatchingFileResources|-PathMatchingResourcePatternResolver#doFindMatchingFileSystemResources|-PathMatchingResourcePatternResolver#retrieveMatchingFiles|-PathMatchingResourcePatternResolver#doRetrieveMatchingFiles
复制代码
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set result) throws IOException {for (File content : listDirectory(dir)) {String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {if (!content.canRead()) {}else {// 如果是文件夹,递归调用doRetrieveMatchingFiles(fullPattern, content, result);}}// 如果是文件且文件路径if (getPathMatcher().match(fullPattern, currPath)) {result.add(content);}}
}
复制代码

以上代码比较简单,与我们平常遍历文件的方式是一样的。

值得一提的是,getPathMatcher().match(fullPattern, currPath)最终调用到的是AntPathMatcher#doMatch,这是一个ant风格的路径匹配验证,即路径中带有*,如传入的pattern是/xxx/spring-framework/spring-learn/build/classes/java/main/org/springframework/learn/demo01/**/*.class,表示匹配/xxx/spring-framework/spring-learn/build/classes/java/main/org/springframework/learn/demo01/及其子文件夹下所有以.class文件结尾的文件,当前传入的path是/xxx/spring-framework/spring-learn/build/classes/java/main/org/springframework/learn/demo01/BeanObj2.class,显然匹配。关于AntPathMatcher#doMatch方法是如何进行匹配的,这里就不进行展开了。

经过了以上步骤,我们终于得到了class文件对应的Resource了.

1.3 将 Resource 转化为 BeanDefinition

将 Resource 转化为 BeanDefinition,代码是

ClassPathScanningCandidateComponentProvider#scanCandidateComponents

// 从 resource 得到 MetadataReader
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);// 这里做了两件事:
// 1. 是否需要初始化为spring bean,即是否有 @Component、@Service等注解
// 2. 查看配置类是否有@Conditional一系列的注解,然后是否满足注册Bean的条件
if (isCandidateComponent(metadataReader)) {// 将 metadataReader 转换为 ScannedGenericBeanDefinition,这也是BeanDefinition家族中的一员ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);...
}
复制代码

1. 从 Resource 得到 MetadataReader

我们追踪下MetadataReader的获取:

|-ClassPathScanningCandidateComponentProvider#scanCandidateComponents|-CachingMetadataReaderFactory#getMetadataReader|-SimpleMetadataReaderFactory#getMetadataReader(Resource)|-SimpleMetadataReader#SimpleMetadataReader
复制代码

代码最终运行到了SimpleMetadataReader的构造方法:

SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);// 这里发生了class文件的读取与解析getClassReader(resource).accept(visitor, PARSING_OPTIONS);this.resource = resource;this.annotationMetadata = visitor.getMetadata();
}
复制代码

再进一步追踪,发现class文件的读取与解析发生在ClassReader类:

这个类使用asm来读取class文件,代码比较复杂,就不深究了。

一直以来,我都以为spring是通过反射来获取类信息的,到这里才知道,原来spring是通过asm直接读取class文件来获取类的信息的

最后我们来看下得到的MetadataReader的结果:

这里重点关注annotations属性,里面有一个annotationsmappingsannotations内容为@Servicemappings是一个数组,内容为

0-@Service
1-@Component
2-@Index
复制代码

annotations本人猜测是 BeanObj1上的注解:

至于mappings是啥,我不好猜测,不过也可以从注解中发现一些端倪:

@Service上有@Component注解,@Component上有@Indexed,而这三者都出现在了mappings中,这看着像是专门用来保存拿注解之上的注解的?不纠结这个了,暂且就当作是这功能吧!注意:mappings里面的内容很重要,后面会用来!

2. isCandidateComponent(MetadataReader):判断是否需要实例化为spring bean

在上一步中,我们得到了basePackage下所有类MetadataReader描述文件,注意这里是所有类,但这些类是不是都要转成spring bean,托管到spring容器呢?这就是isCandidateComponent(MetadataReader)的功能了。废话少说,上代码:

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {// 省略部分代码for (TypeFilter tf : this.includeFilters) {// 这里判断是否需要托管到spring容器if (tf.match(metadataReader, getMetadataReaderFactory())) {// 判断是否有@Conditional一系列的注解return isConditionMatch(metadataReader);}}return false;
}
复制代码

这段主要是做了两个判断:

  • 是否需要为spring bean
  • 是否有@Conditional等一系列的注解

这里我们先来看第一个判断。

在spring中,标明spring bean的注解有很多,像@Component@Repository@Service@Controller@Configuration,甚至是你自己写的注解类,只要上面标了这些注解,像

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 添加 @Component 或 @Service 或 @Repository 等其中之一
@Component
public @interface MySpringBean {...
}
复制代码

都能被spring识别。如果是spring提供的注解(@Component@Repository等),在判断是不是spring bean时,只需要做类似

if(annotation == Component.class || annotation == Repository.class) {...
}
复制代码

的判断就行了。但对于自定义的注解@MySpringBean,spring是怎么知道这是spring bean呢?在我们定义@MySpringBean时,一定要在类上添加@Component@Service@Repository 等其中之一才能被spring识别,这其中有什么玄机呢?我们跟进代码AbstractTypeHierarchyTraversingFilter#match(MetadataReader, MetadataReaderFactory),这里我们对不重要的代码依旧只给出调用链:

|-ClassPathScanningCandidateComponentProvider#isCandidateComponent(MetadataReader)|-AbstractTypeHierarchyTraversingFilter#match(MetadataReader, MetadataReaderFactory)|-AnnotationTypeFilter#matchSelf
复制代码

代码最终到了AnnotationTypeFilter#matchSelf:

@Override
protected boolean matchSelf(MetadataReader metadataReader) {AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();// 这里的annotationType就是 @Componentreturn metadata.hasAnnotation(this.annotationType.getName()) ||(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
复制代码

关键就在这了:

metadata.hasAnnotation(this.annotationType.getName())
与
this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())
复制代码

我们先看metadata.hasAnnotation(this.annotationType.getName())的比较:

// AnnotationMetadata#hasAnnotation
default boolean hasAnnotation(String annotationName) {return getAnnotations().isDirectlyPresent(annotationName);
}
复制代码

这里的getAnnotations()得到的结果是

mappings里的内容是

0-@Service
1-@Component
2-@Index
复制代码

这其实就是我们前面得到的MetadataReader里的内容!

再追踪下去,发现isDirectlyPresent就是判断annotationsmappings里有没有出现@Component:

private boolean isPresent(Object requiredType, boolean directOnly) {// 判断 annotations 里有没有出现 @Componentfor (MergedAnnotation annotation : this.annotations) {Class type = annotation.getType();if (type == requiredType || type.getName().equals(requiredType)) {return true;}}if (!directOnly) {// 判断 mappings 里有没有出现 @Componentfor (AnnotationTypeMappings mappings : this.mappings) {for (int i = 1; i < mappings.size(); i++) {AnnotationTypeMapping mapping = mappings.get(i);if (isMappingForType(mapping, requiredType)) {return true;}}}}return false;
}
复制代码

接着我们再来看this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()),查看调用:

|-AnnotationTypeFilter#matchSelf|-AnnotationMetadata#hasMetaAnnotation|-MergedAnnotationsCollection#get(String, Predicate)|-MergedAnnotationsCollection#get(String, Predicate, MergedAnnotationSelector)|-MergedAnnotationsCollection#find
复制代码

最终的查找方法在MergedAnnotationsCollection#find:

private  MergedAnnotation find(Object requiredType,@Nullable Predicate> predicate,@Nullable MergedAnnotationSelector selector) {MergedAnnotation result = null;for (int i = 0; i < this.annotations.length; i++) {MergedAnnotation root = this.annotations[i];AnnotationTypeMappings mappings = this.mappings[i];// mappings 遍历 mappingsfor (int mappingIndex = 0; mappingIndex < mappings.size(); mappingIndex++) {AnnotationTypeMapping mapping = mappings.get(mappingIndex);if (!isMappingForType(mapping, requiredType)) {continue;}// 到这里,就是找到了 @Component 注解MergedAnnotation candidate = (mappingIndex == 0? (MergedAnnotation) root: TypeMappedAnnotation.createIfPossible(mapping, root, IntrospectionFailureLogger.INFO));if (candidate != null && (predicate == null || predicate.test(candidate))) {if (selector.isBestCandidate(candidate)) {return candidate;}result = (result != null ? selector.select(result, candidate) : candidate);}}}return result;
}
复制代码

可以看到,查找方式跟上面的metadata.hasAnnotation(this.annotationType.getName())高度相似。

以上就是spring用来判断是否包含@Service@Component等注解的逻辑了。

java中,注解是不能继承的,如

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Base {}@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Child extends  Base {}
复制代码

以上语法在java中不被允许的,spring 就是采用这解析注解的注解的方式,实现了类似于继承的功能。

接着我们再来看ClassPathScanningCandidateComponentProvider#isConditionMatch方法。实际上,这个方法是用来判断类是否含有@Conditional注解的,满足条件则会识别为spring bean,代码最终调用到了ConditionEvaluator#shouldSkip(AnnotatedTypeMetadata, ConfigurationCondition.ConfigurationPhase):

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {// 省略了一些代码// 得到 condition 对象List conditions = new ArrayList<>();for (String[] conditionClasses : getConditionClasses(metadata)) {for (String conditionClass : conditionClasses) {Condition condition = getCondition(conditionClass, this.context.getClassLoader());conditions.add(condition);}}}AnnotationAwareOrderComparator.sort(conditions);// 遍历,判断 condition 条件是否成立for (Condition condition : conditions) {ConfigurationPhase requiredPhase = null;if (condition instanceof ConfigurationCondition) {requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();}if ((requiredPhase == null || requiredPhase == phase) // 判断 condition 条件是否成立,一个条件满足就返回true&& !condition.matches(this.context, metadata)) {return true;}}return false;
}// 通过反射获取 Condition 对象
private Condition getCondition(String conditionClassName, @Nullable ClassLoader classloader) {Class conditionClass = ClassUtils.resolveClassName(conditionClassName, classloader);return (Condition) BeanUtils.instantiateClass(conditionClass);
}
复制代码

这里做了两件事:

  1. 获取 condition 对象
  2. 遍历 condition对象,调用condition.matches()方法,判断条件是否成立

3. 从 MetadataReader 得到 ScannedGenericBeanDefinition

这里仅是做了一个简单的赋值,看下 ScannedGenericBeanDefinition 的构造方法就明白了:

ScannedGenericBeanDefinition#ScannedGenericBeanDefinition

public ScannedGenericBeanDefinition(MetadataReader metadataReader) {Assert.notNull(metadataReader, "MetadataReader must not be null");this.metadata = metadataReader.getAnnotationMetadata();setBeanClassName(this.metadata.getClassName());
}
复制代码

代码比较简单,就不多做分析了。

2. 丰富beanDefinition信息

历经千难万险,终于得到了beanDefinition,但此时beanDefinition并不丰富,接下来就是进一步扩展 beanDefinition的信息了。这些信息包括bean的名称bean的作用域@Lazy 注解、@Primary注解、@DependsOn注解等,代码如下:

public abstract class AnnotationConfigUtils {.../*** 进一步丰富 BeanDefinition*/static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {// 处理 @LazyAnnotationAttributes lazy = attributesFor(metadata, Lazy.class);if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));}else if (abd.getMetadata() != metadata) {lazy = attributesFor(abd.getMetadata(), Lazy.class);if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));}}// 处理 @Primaryif (metadata.isAnnotated(Primary.class.getName())) {abd.setPrimary(true);}// 处理 @DependsOnAnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);if (dependsOn != null) {abd.setDependsOn(dependsOn.getStringArray("value"));}// 处理 @RoleAnnotationAttributes role = attributesFor(metadata, Role.class);if (role != null) {abd.setRole(role.getNumber("value").intValue());}// 处理 @DescriptionAnnotationAttributes description = attributesFor(metadata, Description.class);if (description != null) {abd.setDescription(description.getString("value"));}}
}
复制代码

3. registerBeanDefinition(definitionHolder, this.registry): 添加 BeanDefinition 到 beanFactory

BeanDefinitionbeanFactory的操作比较简单,关键的代码如下:

|-ClassPathBeanDefinitionScanner#registerBeanDefinition|-BeanDefinitionReaderUtils#registerBeanDefinition|-GenericApplicationContext#registerBeanDefinition|-DefaultListableBeanFactory#registerBeanDefinition
复制代码

DefaultListableBeanFactory#registerBeanDefinition

this.beanDefinitionMap.put(beanName, beanDefinition);
复制代码

ClassPathBeanDefinitionScanner#registerBeanDefinitionDefaultListableBeanFactory#registerBeanDefinition,这其中虽然经历了一些弯弯绕绕,但依旧不妨碍我们找到关键的代码。

到此,磁盘上的class文件,经过spring扫描,终于变成了BeanDefinition,保存在BeanFactory中了。

4. 总结

本文比较长,主要分析了spring 扫描包路径得到beanDefinition的过程,主要流程如下:

  1. 根据包名得到路径Resource
  2. 根据路径Resouce得到该路径下所有class文件的Resouce
  3. 根据class文件的Resouce通过asm解析得到MetadataReader,注意:这里的MetadataReader还是所有class文件的MetadataReader
  4. MetadataReader中找到需要spring托管的MetadataReader,将其转化为ScannedGenericBeanDefinitionScannedGenericBeanDefinitionBeanDefinition的子类;
  5. 进一步丰富 ScannedGenericBeanDefinition 的信息;
  6. 将上面得到的BeanDefinition添加到BeanFactory

至此,包名转换为BeanDefinition完成。

本文还有两个值得注意的地方:

  1. spring 在获取类上的注解时,不是通过反射,而是使用asm直接解析class文件,然后再获取类上的注解的
  2. 在处理注解时,spring通过解析“注解的注解”实现了一套类似于注解继承的方式,这也是spring能识别@Component@Service甚至是开发者自定义注解的原因。

得到了BeanDefinition后,接着就是spring容器的初始化了,我们下篇文章再见。

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
苁蓉的食用方法 鲜苁蓉的吃法有... 肉苁蓉号称“温而不热,补而不峻,暖而不燥,滑而不泄,故有从容之名。”历来也是传统药食同源的滋补佳品。...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
阳澄湖在哪里哪个省的 阳澄湖是... 点击题目下方苏州生活指南有用 有趣 有态度近期的阳澄湖度很高,3月24日,央视二套《第一时间》直播连...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...