Spring @Configuration 和 @Component 区别

Spring @Configuration 和 @Component 区别

一句话概括就是 @Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。

 

@Configuration 注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    String value() default "";
}


从定义来看, @Configuration 注解本质上还是 @Component,因此 <context:component-scan/> 或者 @ComponentScan 都能处理@Configuration 注解的类。

@Configuration 标记的类必须符合下面的要求:

    配置类必须以类的形式提供(不能是工厂方法返回的实例),允许通过生成子类在运行时增强(cglib 动态代理)。
    配置类不能是 final 类(没法动态代理)。
    配置注解通常为了通过 @Bean 注解生成 Spring 容器管理的类,
    配置类必须是非本地的(即不能在方法中声明,不能是 private)。
    任何嵌套配置类都必须声明为static。
    @Bean 方法可能不会反过来创建进一步的配置类(也就是返回的 bean 如果带有 @Configuration,也不会被特殊处理,只会作为普通的 bean)。

 

 

@Component 注意

@Component 注解并没有通过 cglib 来代理@Bean 方法的调用,因此像下面这样配置时,就是两个不同的 country。

@Component
public class MyBeanConfig {

    @Bean
    public Country country(){
        return new Country();
    }

    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }

}

有些特殊情况下,我们不希望 MyBeanConfig 被代理(代理后会变成WebMvcConfig$$EnhancerBySpringCGLIB$$8bef3235293)时,就得用 @Component,这种情况下,上面的写法就需要改成下面这样:

@Component
public class MyBeanConfig {

    @Autowired
    private Country country;

    @Bean
    public Country country(){
        return new Country();
    }

    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country);
    }

}

 

加载过程

Spring 容器在启动时,会加载默认的一些 PostPRocessor,其中就有 ConfigurationClassPostProcessor,这个后置处理程序专门处理带有 @Configuration 注解的类,这个程序会在 bean 定义加载完成后,在 bean 初始化前进行处理。主要处理的过程就是使用 cglib 动态代理增强类,而且是对其中带有 @Bean 注解的方法进行处理。
,查找到所有带有 @Configuration 注解的 bean 定义,然后在第二个 for 循环中,通过下面的方法对类进行增强:

使用增强后的类替换了原有的 beanClass:所以到此时,所有带有 @Configuration 注解的 bean 都已经变成了增强的类。也就是当方法有 @Bean 注解的时候,就会执行这个回调方法。

@Bean 注解方法执行策略

先给一个简单的示例代码:@Configuration
public class MyBeanConfig {

    @Bean
    public Country country(){
        return new Country();
    }

    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }

}相信大多数人第一次看到上面 userInfo() 中调用 country() 时,会认为这里的 Country 和上面 @Bean 方法返回的 Country 可能不是同一个对象,因此可能会通过下面的方式来替代这种方式:

@Autowired
private Country country;

实际上不需要这么做(后面会给出需要这样做的场景),直接调用 country() 方法返回的是同一个实例。

pring中,对于配置类来讲,其实是有分类的,大体可以分为两类,一类称为LITE模式,另一类称为FULL模式,那么对应上面的注解,@Component就是LITE类型,@Configuration就是FULL类型,如何理解这两种配置类型呢?

 

当我们使用@Component实现配置类时:

从结果可知,foo()方法执行了两次,一次是bean方法执行的,一次是eoo()调用执行的,所以两次生成的foo对象是不一样的。很符合大家的预期,

但是当我们使用@Configuration标注配置类时,执行结果如下:这里可以看到foo()方法只执行了一次,同时eoo()方法调用foo()生成的foo对象是同一个。

 

这也就是@Component@Configuration的区别现象展示,那么为什么会有这样的一个现象?

 

我们来考虑一个问题,就是eoo()方法中调用了foo()方法,很明显这个foo()这个方法就是会形成一个新对象

,假设我们调用的foo()方法不是原来的foo()方法,

是不是就可能不会形成新对象?

 

如果我们在调用foo()方法的时候去容器中获取一下foo这个Bean,是不是就可以达到这样的效果?

那如何才能达到这样的效果呢?有一个方法,代理

换句话说,我们调用的eoo()和foo()方法,包括AppConfig都被Spring代理了

,那么这里我们明白了@Component与@Configuration最根本的区别,

那就是@Configuration标注的类会被Spring代理,

其实这样描述不是非常严谨,更加准确的来说应该是

3.Spring如何实现FULL配置的代理

如果要明白这一点,那么还需要明确一个前提,就是Spring在什么时间将这些配置类转变成FULL模式或者LITE模式的,接下来我们就要介绍个人认为在Spring中非常重要的一个类,现在需要知道的是ConfigurationClassPostProcessor这个类是在什么时间被实例化的?

3.2ConfigurationClassPostProcessor在什么时间被实例化

要回答这个问题,需要先明确一个前提,那就是ConfigurationClassPostProcessor这个类对应的BeanDefinition在什么时间注册到Spring的容器中的,因为Spring的实例化比较特殊,主要是基于BeanDefinition来处理的这个类是在什么时间被注册为一个Beandefinition的?这个可以在源代码中找到答案,具体其实就是在初始化这个Spring容器的时候。。

如果一个类的BeanDefinition的Attribute中有Full配置属性,那么这个类就会被Spring代理

我们讲了Spring是通过对BeanDefinition进行解析,处理,实例化,填充,初始化以及众多回调等等步骤才会形成一个Bean,那么现在ConfigurationClassPostProcessor既然已经形成了一个BeanDefinition。

 

 

这两个方法应该就是ConfigurationClassPostProcessor最为关键的了,我们在这里先简单的总结一下,第一个方法主要完成了内部类,@Component,@ComponentScan,@Bean,@Configuration,@Import等等注解的处理,然后生成对应的BeanDefinition,另一个方法就是对@Configuration使用CGLIB进行增强,那我们先来看Spring是在哪里区分配置的LITE模式和FULL模式?

 

想想看,BeanDefinition主要是做什么的?这个主要是用来描述Class对象的,例如这个Class是不是抽象的,作用域是什么,是不是懒加载等等信息,那如果一个Class对象有一个“属性”是BeanDefinition描述不了的,那这个要如何处理呢?那这个接口AttributeAccessor又派上用场了,你可以向其中存放任何你定义的数据,可以理解为一个map,现在了解BeanDefinition的属性的含义了么?

在这里也能看到@Configuration(proxyBeanMethods = false)@Component一样效果,都是LITE模式

@Component在Spring中是代表LITE模式的配置注解,这种模式下的注解不会被Spring所代理,就是一个标准类,如果在这个类中有@Bean标注的方法,那么方法间的相互调用,其实就是普通Java类的方法的调用。

@Configuration在Spring中是代表FULL模式的配置注解,这种模式下的类会被Spring所代理,那么在这个类中的@Bean方法的相互调用,就相当于调用了代理方法,那么在代理方法中会判断,是否调用getBean方法还是invokeSuper方法,这里就是这两个注解的最根本的区别。

一句话概括就是 @Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。
posted @ 2024-03-01 10:12  予真  阅读(43)  评论(0编辑  收藏  举报