首页 > 其他分享 >@Component生效逻辑

@Component生效逻辑

时间:2022-09-22 11:34:11浏览次数:46  
标签:逻辑 SpringBootApplication Component bean 生效 注解 class BeanDefinition

前言

在SpringBoot中,只需要一个简单的启动类,就能自动完成很多复杂的工作, 其中就有自动扫描classpath,将带有@Component的类生成bean.

@SpringBootApplication
public class SpringLifecycleApplication {

    public static void main(String[] args) {
        ApplicationContext applicationContext = SpringApplication.run(SpringLifecycleApplication.class, args);
    }

}
//自动注册为bean
@Component
public class SimpleBean{
    
}

本文将探究SpringBoot中@Component的生效逻辑.

正文

@SpringBootApplication简介

@SpringBootApplication注解是SpringBoot的关键,它组合了@Configuration,@ComponentScan等注解,有必要提前了解一下,源码如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

这里要提一下,Spring对注解的处理通常是递归的,例如查找SpringLifecycleApplication这个类是否被@Configuration注解,会经历以下几步

查找SpringLifecycleApplication的注解, 找到@SpringBootApplication,不是需要的@Configuration,继续

查找修饰@SpringBootApplication的注解, 找到@SpringBootConfiguration,@ComponentScan等注解

还是没有符合的,再查看修饰这些注解的注解~ 如此递归查找,在@SpringBootConfiguration中找到@Configuration,成功

类似的还有@Service,@Controller.它们都被@Component修饰,因此查找包含@Component注解的类时它们也是符合的.

@Component的生效逻辑

下面将按照SpringBoot的启动流程讲解@Component,参见下图

  SpringBoot-bean.png

1. createApplicationContext阶段: 注册ConfigurationClassPostProcessor

SpringBoot默认创建AnnotationConfigApplicationContext,它在构造时会创建AnnotatedBeanDefinitionReader,后者构造时会调用AnnotationConfigUtils.registerAnnotationConfigProcessors()注册ConfigurationClassPostProcessor的BeanDefinition.

ConfigurationClassPostProcessor会处理带@Configuartion修饰的bean,下面会用到

  SpringBoot-bean-0.png

2. prepareContext阶段: 注册启动类SpringLifeCycleApplication的BeanDefinition

在Spring启动的早期阶段,就会将SpringLifeCycleApplication注册为bean,后续的很多逻辑都会根据它身上的@SpringBootApplication(以及其他注解)去做

  SpringBoot-bean-1.png

3. refreshContext阶段的invokeBeanFactoryPostProcessor(): 调用ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()

  SpringBoot-bean-2.png

插一段,先来理一下invokeBeanFactoryPostProcessor()的具体逻辑,分为两块: postProcessBeanDefinitionRegistry()和postProcessBeanFactory()

  • postProcessBeanDefinitionRegistry()允许添加自定义的BeanDefinition(对于通过xml注册的BeanDefinition或代码注册的称之为正常,其他的如通过注解@Component添加的都称为自定义),经过这一步,Spring不再允许添加BeanDefinition,由于新注册的BeanDefinition可能代表一个BeanFactoryPostProcessor,因此这个过程是迭代进行的,具体逻辑是:

    执行所有BeanFactoryPostProcessor,如果执行后有新的BeanFactoryPostProcessor生成,执行它们,循环进行直到没有新的BeanFactoryPostProcessor生成

  • postProcessBeanFactory()允许对现有的BeanDefinition做修改,这个是一次性全部调用

这里有一个涉及到bean生命周期的问题, 如果一个BeanFactoryPostProcessor通过@Autowired依赖其他bean, 会生效吗?

@Component
public class SimpleBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Autowired
    private SimpleBean simpleBean;
}

不会,BeanFactoryPostProcessor初始化时还没有 BeanpostProcessor,而@Autowired又是依赖AutowiredAnnotationBeanPostProcessor进行注入,这就导致BeanFactoryPostProcessor中的依赖不会被注入,simpleBean一直为null

回归正文,这一步生成并调用前面注册的ConfigurationClassPostProcessor,它逻辑是: 查找现有的BeanDefinition(是包含SpringLifecycleApplication的BeanDefinition),对带有@Configuration注解的,使用ConfigurationClassParser进行解析处理,判断是否为满足@Configuration注解要求的逻辑如下

    public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
        return metadata.isAnnotated(Configuration.class.getName());
    }
    //StandardAnnotationMetadata
    public boolean isAnnotated(String annotationName) {
        return (this.annotations.length > 0 &&
                AnnotatedElementUtils.isAnnotated(getIntrospectedClass(), annotationName));
    }
    //这个方法会递归寻找注解. 如SpringLifeCycleApplication只有注解@SpringBootApplication,没找到@Configuration
    //就会去寻找@SpringBootApplication这个注解包含的注解,按此规律递归寻找直至找到或者递归终止,对于@SpringBootApplication  
    //它拥有的注解@SpringBootConfiguration拥有@Configuration注解,因此最终是符合条件的
    public static boolean isAnnotated(AnnotatedElement element, String annotationName) {
        return Boolean.TRUE.equals(searchWithGetSemantics(element, null, annotationName, alwaysTrueAnnotationProcessor));
    }

4. ConfigurationClassParser解析启动类,获取@ComponScan注解,将解析工作委托给ComponentScanAnnotationParser

由于@SpringBootApplication是被@ComponentScan注解的,这块逻辑会解析到.实际的处理操作会委托给ComponentScanAnnotationParser,由它扫描BeanDefinition

        // Process any @ComponentScan annotations
        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() &&
                !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            for (AnnotationAttributes componentScan : componentScans) {
                  // ### 扫描生成一些新的BeanDefinition ###     
                // The config class is annotated with @ComponentScan -> perform the scan immediately
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }

5. ComponentScanAnnotationParser解析@ComponentScan的属性,获取到BasePackage再使用ClassPathBeanDefinitionScanner进行扫描

这里主要解析出BasePackage,然后将工作委托给ClassPathBeanDefinitionScanner,默认BasePackage就是被@SpringBootApplication注解的这个类所在的包位置,我们这里就是com.github.alonwang.springlifecycle

  SpringBoot-bean-3.png

6. ClassPathBeanDefinitionScanner扫描BasePackage路径,获取到符合条件的Bean

  SpringBoot-bean-4.png

这里会扫描BasePackage下的所有class,将所有符合条件的class封装起来,注册到容器中

protected 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) {
             //找到包下所有符合条件的类,生成其BeanDefinition
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                //...
                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;
    }

这里的条件由excludeFilters和includeFilters共同限定,默认includeFilter在构造ClassPathBeanDefinitionScanner时添加,包含@Component的AnnotationTypeFilter. 它也遵循Spring注解递归查找的原则, 因为@Controller,@Service也是被@Component注解修饰的,因此他们也能被扫描到

    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }

至此@Component生效逻辑讲解完毕.



作者:alonwang
链接:https://www.jianshu.com/p/723758807fd1
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

标签:逻辑,SpringBootApplication,Component,bean,生效,注解,class,BeanDefinition
From: https://www.cnblogs.com/anenyang/p/16718609.html

相关文章

  • flask 博客系统 逻辑关系
    主干部门提交代码评论的逻辑......
  • 逻辑回归函数
    SigmoidFunctionLogisticFunction:\(S(x)=\frac{1}{1+e^{-x}}\)(\(S(0)=\frac{1}{1+1}=0.5\))Hyperbolictangent\(f(x)=tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}\)......
  • PostgreSQL逻辑复制解密
    在数字化时代的今天,我们都认同数据会创造价值。为了最大化数据的价值,我们不停的建立着数据迁移的管道,从同构到异构,从关系型到非关系型,从云下到云上,从数仓到数据湖,试图在各......
  • 编程逻辑
    大部分高级编程语言虽然语法不同,编译器不同,学习它们的小哥哥小姐姐们不同,但有一点却是出奇地一致:编程逻辑!有些刚入行或刚入门的童鞋可能连编程是啥意思都没弄懂,一下子又来......
  • VueRouter 报错:inject() can only be used inside setup() or functional components
    单独创建的一个文件,封装了登录函数的业务逻辑,出现了一个警告,紧接着就是报错:说不能读取到路由的push函数。路由必须在组件里面使用,不能在ts或js文件中使用。还要注......
  • 逻辑运算符
      假设变量A=1,变量B=0,则存在途中实例。切记:真为1,假为0。    ......
  • 编码规范:不要用参数控制代码逻辑
    用参数控制代码逻辑可能是最经典的错误编码习惯,我在公司的项目代码中见到过好几次类似编码,包括我本人在职业生涯初期也编写过类似的代码。什么叫参数控制代码逻辑?我们可能......
  • 断点续传 上传逻辑及代码
    整体逻辑如下    前台引用spark-md5获取文件唯一ID值,即md5值,前台将文件进行分片,通过该值进行后台校验,以此实现断点续传。    前台计算MD5,前台计算MD5快慢......
  • GB28181国标视频监控平台LiveGBS用户自定义播放回调鉴权,允许用户按照自己的业务逻辑控
    GB28181流媒体平台LiveGBS中有是否需要用户登录认证才可播放视频的选项控制。但是很多情况不能满足实际项目使用场景中对播放权限的控制,允许谁播不允许谁播等可能有更详细......
  • DNS解析为什么不生效?DNS解析不生效原因分析
    网站页面为什么打不开?刚修改过域名解析,为什么不生效?如何查看解析是否生效?很多企业在网站的实际运营中,经常会遇到以上DNS解析问题,给网站的运营管理人员造成诸多困扰,接下来中......