• spring启动流程(二):包的扫描流程


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

    依旧是AnnotationConfigApplicationContext的构造方法:

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

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

    AnnotationConfigApplicationContext#scan

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

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

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

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

    1. AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)
    2. |-AnnotationConfigApplicationContext#scan
    3. |-ClassPathBeanDefinitionScanner#scan
    4. |-ClassPathBeanDefinitionScanner#doScan
    5. 复制代码

    ClassPathBeanDefinitionScanner#doScan 代码如下:

    1. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    2. Assert.notEmpty(basePackages, "At least one base package must be specified");
    3. Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    4. //遍历需要扫描的包路径
    5. for (String basePackage : basePackages) {
    6. //获取所有符合条件的BeanDefinition
    7. Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    8. for (BeanDefinition candidate : candidates) {
    9. //绑定BeanDefinition与Scope
    10. ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
    11. candidate.setScope(scopeMetadata.getScopeName());
    12. //查看是否配置类是否指定bean的名称,如没指定则使用类名首字母小写
    13. String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
    14. //下面两个if是处理lazy、Autowire、DependencyOn、initMethod、enforceInitMethod、destroyMethod、
    15. // enforceDestroyMethod、Primary、Role、Description这些逻辑的
    16. if (candidate instanceof AbstractBeanDefinition) {
    17. postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
    18. }
    19. if (candidate instanceof AnnotatedBeanDefinition) {
    20. AnnotationConfigUtils.processCommonDefinitionAnnotations(
    21. (AnnotatedBeanDefinition) candidate);
    22. }
    23. //检查bean是否存在
    24. if (checkCandidate(beanName, candidate)) {
    25. //又包装了一层
    26. BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
    27. //检查scope是否创建,如未创建则进行创建
    28. definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(
    29. scopeMetadata, definitionHolder, this.registry);
    30. beanDefinitions.add(definitionHolder);
    31. //注册 beanDefinition
    32. registerBeanDefinition(definitionHolder, this.registry);
    33. }
    34. }
    35. }
    36. return beanDefinitions;
    37. }
    38. 复制代码

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

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

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

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

    1. 根据包路径得到 BeanDefinition

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

    1. AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)
    2. |-AnnotationConfigApplicationContext#scan
    3. |-ClassPathBeanDefinitionScanner#scan
    4. |-ClassPathBeanDefinitionScanner#doScan
    5. |-ClassPathScanningCandidateComponentProvider#findCandidateComponents
    6. |-ClassPathScanningCandidateComponentProvider#scanCandidateComponents
    7. 复制代码

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

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

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

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

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

    1.1 根据basePackage得到包扫描路径

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

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

    1.2 扫描包路径

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

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

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

    1. AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)
    2. |-AnnotationConfigApplicationContext#scan
    3. |-ClassPathBeanDefinitionScanner#scan
    4. |-ClassPathBeanDefinitionScanner#doScan
    5. |-ClassPathScanningCandidateComponentProvider#findCandidateComponents
    6. |-ClassPathScanningCandidateComponentProvider#scanCandidateComponents
    7. |- GenericApplicationContext#getResources
    8. |-AbstractApplicationContext#getResources
    9. |-PathMatchingResourcePatternResolver#getResources
    10. |-PathMatchingResourcePatternResolver#findPathMatchingResources
    11. 复制代码

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

    1. protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
    2. // 传入的 locationPattern 是 classpath*:org/springframework/learn/demo01/**/*.class
    3. // rootDirPath 是 classpath*:org/springframework/learn/demo01/
    4. String rootDirPath = determineRootDir(locationPattern);
    5. // subPattern 是 **/*.class
    6. String subPattern = locationPattern.substring(rootDirPath.length());
    7. // 这里返回的 Resource 是 rootDirPath 的绝对路径(用url表示)
    8. // URL [file:/xxx/spring-learn/build/classes/java/main/org/springframework/learn/demo01/]
    9. Resource[] rootDirResources = getResources(rootDirPath);
    10. Set<Resource> result = new LinkedHashSet<>(16);
    11. for (Resource rootDirResource : rootDirResources) {
    12. rootDirResource = resolveRootDirResource(rootDirResource);
    13. URL rootDirUrl = rootDirResource.getURL();
    14. if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
    15. URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
    16. if (resolvedUrl != null) {
    17. rootDirUrl = resolvedUrl;
    18. }
    19. rootDirResource = new UrlResource(rootDirUrl);
    20. }
    21. // 处理 vfs 资源查找
    22. if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    23. result.addAll(VfsResourceMatchingDelegate
    24. .findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
    25. }
    26. // 处理jar包文件查找
    27. else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
    28. result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
    29. }
    30. // 处理文件路径下的文件查找
    31. else {
    32. result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
    33. }
    34. }
    35. return result.toArray(new Resource[0]);
    36. }
    37. 复制代码

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

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

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

    1. |-PathMatchingResourcePatternResolver#getResources
    2. |-PathMatchingResourcePatternResolver#findAllClassPathResources
    3. |-PathMatchingResourcePatternResolver#doFindAllClassPathResources
    4. 复制代码

    最终代码到了PathMatchingResourcePatternResolver#doFindAllClassPathResources:

    1. protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
    2. Set<Resource> result = new LinkedHashSet<>(16);
    3. ClassLoader cl = getClassLoader();
    4. // path对应的url
    5. Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) :
    6. ClassLoader.getSystemResources(path));
    7. while (resourceUrls.hasMoreElements()) {
    8. URL url = resourceUrls.nextElement();
    9. // 将url转换为Resource,并添加到结果中
    10. result.add(convertClassLoaderURL(url));
    11. }
    12. if ("".equals(path)) {
    13. addAllClassLoaderJarRoots(cl, result);
    14. }
    15. return result;
    16. }
    17. // 将url转换为Resource
    18. protected Resource convertClassLoaderURL(URL url) {
    19. return new UrlResource(url);
    20. }
    21. 复制代码

    此时传入的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方法:

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

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

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

    1. |-PathMatchingResourcePatternResolver#findPathMatchingResources
    2. |-PathMatchingResourcePatternResolver#doFindPathMatchingFileResources
    3. |-PathMatchingResourcePatternResolver#doFindMatchingFileSystemResources
    4. |-PathMatchingResourcePatternResolver#retrieveMatchingFiles
    5. |-PathMatchingResourcePatternResolver#doRetrieveMatchingFiles
    6. 复制代码
    1. protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result)
    2. throws IOException {
    3. for (File content : listDirectory(dir)) {
    4. String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
    5. if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
    6. if (!content.canRead()) {
    7. }
    8. else {
    9. // 如果是文件夹,递归调用
    10. doRetrieveMatchingFiles(fullPattern, content, result);
    11. }
    12. }
    13. // 如果是文件且文件路径
    14. if (getPathMatcher().match(fullPattern, currPath)) {
    15. result.add(content);
    16. }
    17. }
    18. }
    19. 复制代码

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

    值得一提的是,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

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

    1. 从 Resource 得到 MetadataReader

    我们追踪下MetadataReader的获取:

    1. |-ClassPathScanningCandidateComponentProvider#scanCandidateComponents
    2. |-CachingMetadataReaderFactory#getMetadataReader
    3. |-SimpleMetadataReaderFactory#getMetadataReader(Resource)
    4. |-SimpleMetadataReader#SimpleMetadataReader
    5. 复制代码

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    1. @Target(ElementType.TYPE)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. // 添加 @Component 或 @Service 或 @Repository 等其中之一
    5. @Component
    6. public @interface MySpringBean {
    7. ...
    8. }
    9. 复制代码

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

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

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

    1. |-ClassPathScanningCandidateComponentProvider#isCandidateComponent(MetadataReader)
    2. |-AbstractTypeHierarchyTraversingFilter#match(MetadataReader, MetadataReaderFactory)
    3. |-AnnotationTypeFilter#matchSelf
    4. 复制代码

    代码最终到了AnnotationTypeFilter#matchSelf:

    1. @Override
    2. protected boolean matchSelf(MetadataReader metadataReader) {
    3. AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
    4. // 这里的annotationType就是 @Component
    5. return metadata.hasAnnotation(this.annotationType.getName()) ||
    6. (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
    7. }
    8. 复制代码

    关键就在这了:

    1. metadata.hasAnnotation(this.annotationType.getName())
    2. this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())
    3. 复制代码

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

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

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

    mappings里的内容是

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

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

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

    1. private boolean isPresent(Object requiredType, boolean directOnly) {
    2. // 判断 annotations 里有没有出现 @Component
    3. for (MergedAnnotation<?> annotation : this.annotations) {
    4. Class<? extends Annotation> type = annotation.getType();
    5. if (type == requiredType || type.getName().equals(requiredType)) {
    6. return true;
    7. }
    8. }
    9. if (!directOnly) {
    10. // 判断 mappings 里有没有出现 @Component
    11. for (AnnotationTypeMappings mappings : this.mappings) {
    12. for (int i = 1; i < mappings.size(); i++) {
    13. AnnotationTypeMapping mapping = mappings.get(i);
    14. if (isMappingForType(mapping, requiredType)) {
    15. return true;
    16. }
    17. }
    18. }
    19. }
    20. return false;
    21. }
    22. 复制代码

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

    1. |-AnnotationTypeFilter#matchSelf
    2. |-AnnotationMetadata#hasMetaAnnotation
    3. |-MergedAnnotationsCollection#get(String, Predicate)
    4. |-MergedAnnotationsCollection#get(String, Predicate, MergedAnnotationSelector)
    5. |-MergedAnnotationsCollection#find
    6. 复制代码

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

    1. private <A extends Annotation> MergedAnnotation<A> find(Object requiredType,
    2. @Nullable Predicate<? super MergedAnnotation<A>> predicate,
    3. @Nullable MergedAnnotationSelector<A> selector) {
    4. MergedAnnotation<A> result = null;
    5. for (int i = 0; i < this.annotations.length; i++) {
    6. MergedAnnotation<?> root = this.annotations[i];
    7. AnnotationTypeMappings mappings = this.mappings[i];
    8. // mappings 遍历 mappings
    9. for (int mappingIndex = 0; mappingIndex < mappings.size(); mappingIndex++) {
    10. AnnotationTypeMapping mapping = mappings.get(mappingIndex);
    11. if (!isMappingForType(mapping, requiredType)) {
    12. continue;
    13. }
    14. // 到这里,就是找到了 @Component 注解
    15. MergedAnnotation<A> candidate = (mappingIndex == 0
    16. ? (MergedAnnotation<A>) root
    17. : TypeMappedAnnotation.createIfPossible(mapping, root, IntrospectionFailureLogger.INFO));
    18. if (candidate != null && (predicate == null || predicate.test(candidate))) {
    19. if (selector.isBestCandidate(candidate)) {
    20. return candidate;
    21. }
    22. result = (result != null ? selector.select(result, candidate) : candidate);
    23. }
    24. }
    25. }
    26. return result;
    27. }
    28. 复制代码

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

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

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

    1. @Target({ElementType.TYPE, ElementType.METHOD})
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. public @interface Base {
    5. }
    6. @Target({ElementType.TYPE, ElementType.METHOD})
    7. @Retention(RetentionPolicy.RUNTIME)
    8. @Documented
    9. public @interface Child extends Base {
    10. }
    11. 复制代码

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

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

    1. public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    2. // 省略了一些代码
    3. // 得到 condition 对象
    4. List<Condition> conditions = new ArrayList<>();
    5. for (String[] conditionClasses : getConditionClasses(metadata)) {
    6. for (String conditionClass : conditionClasses) {
    7. Condition condition = getCondition(conditionClass, this.context.getClassLoader());
    8. conditions.add(condition);
    9. }
    10. }
    11. }
    12. AnnotationAwareOrderComparator.sort(conditions);
    13. // 遍历,判断 condition 条件是否成立
    14. for (Condition condition : conditions) {
    15. ConfigurationPhase requiredPhase = null;
    16. if (condition instanceof ConfigurationCondition) {
    17. requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
    18. }
    19. if ((requiredPhase == null || requiredPhase == phase)
    20. // 判断 condition 条件是否成立,一个条件满足就返回true
    21. && !condition.matches(this.context, metadata)) {
    22. return true;
    23. }
    24. }
    25. return false;
    26. }
    27. // 通过反射获取 Condition 对象
    28. private Condition getCondition(String conditionClassName, @Nullable ClassLoader classloader) {
    29. Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName, classloader);
    30. return (Condition) BeanUtils.instantiateClass(conditionClass);
    31. }
    32. 复制代码

    这里做了两件事:

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

    3. 从 MetadataReader 得到 ScannedGenericBeanDefinition

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

    ScannedGenericBeanDefinition#ScannedGenericBeanDefinition

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

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

    2. 丰富beanDefinition信息

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

    1. public abstract class AnnotationConfigUtils {
    2. ...
    3. /**
    4. * 进一步丰富 BeanDefinition
    5. */
    6. static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd,
    7. AnnotatedTypeMetadata metadata) {
    8. // 处理 @Lazy
    9. AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
    10. if (lazy != null) {
    11. abd.setLazyInit(lazy.getBoolean("value"));
    12. }
    13. else if (abd.getMetadata() != metadata) {
    14. lazy = attributesFor(abd.getMetadata(), Lazy.class);
    15. if (lazy != null) {
    16. abd.setLazyInit(lazy.getBoolean("value"));
    17. }
    18. }
    19. // 处理 @Primary
    20. if (metadata.isAnnotated(Primary.class.getName())) {
    21. abd.setPrimary(true);
    22. }
    23. // 处理 @DependsOn
    24. AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
    25. if (dependsOn != null) {
    26. abd.setDependsOn(dependsOn.getStringArray("value"));
    27. }
    28. // 处理 @Role
    29. AnnotationAttributes role = attributesFor(metadata, Role.class);
    30. if (role != null) {
    31. abd.setRole(role.getNumber("value").intValue());
    32. }
    33. // 处理 @Description
    34. AnnotationAttributes description = attributesFor(metadata, Description.class);
    35. if (description != null) {
    36. abd.setDescription(description.getString("value"));
    37. }
    38. }
    39. }
    40. 复制代码

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

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

    1. |-ClassPathBeanDefinitionScanner#registerBeanDefinition
    2. |-BeanDefinitionReaderUtils#registerBeanDefinition
    3. |-GenericApplicationContext#registerBeanDefinition
    4. |-DefaultListableBeanFactory#registerBeanDefinition
    5. 复制代码

    DefaultListableBeanFactory#registerBeanDefinition

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

    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容器的初始化了,我们下篇文章再见。

  • 相关阅读:
    [001] [RISC-V] Linker Script 链接脚本说明
    MSP430F5529晶振配置
    线段树、树状数组模板(复制粘贴确实好用)
    无代码开发提醒设置入门教程
    1688API接口,获取商品详情,按关键词搜索,拍立淘,商品评论商品类目,店铺接口等
    docker-compose 搭建 Prometheus+Grafana监控系统
    【算法篇-数论】快速幂
    MATLAB 的 plot 绘图
    无痛理解傅里叶变换
    2023-06-10 Untiy进阶 C#知识补充1——.Net介绍
  • 原文地址:https://blog.csdn.net/BASK2312/article/details/127700261