Skip to main content

@Configuration和@Component有什么区别?

作者:程序员马丁

在线博客:https://open8gu.com

note

大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。

面试话术

@Configuration 是基于 @Component 的组合注解,它们都能实现一些类似的功能:

  • 将被注解的类声明为 Spring 容器中的 Bean。
  • 内部带有 @Bean 注解的工厂方法返回的对象,都会被视为 Spring 容器中的 Bean。

它们最主要的区别在于:

  • 基于 @Configuration 的配置方式被称为 Full 模式(Full @Configuration mode)。
  • 基于非 @Configuration 的配置方式称为 Lite 模式(“lite”@Bean mode)。

具体内容可参见官方文档:@Bean and @Configuration sourl.cn/gLF9G2

1. Full 模式

Full 模式即将带有 @Bean 注解的方法声明在带有 @Configuration 注解的类中的形式。

它的主要特点是:

  • 是否代理:配置类本身会通过 ConfigurationClassEnhancer 生成 CGLib 代理对象,因此更加消耗性能。
  • 访问修饰符:类和带有 @Bean 注解的方法都不可以被 privatefinal 修饰(因为要被代理)。
  • 方法的调用结果:不管是外部类调用被 @Bean 注解的方法,还是类内部方法互相调用,这些方法返回的都是 Spring 容器中的 Bean。

举个例子:

@Configuration
public class FullConfiguration {
@Bean
public Foo1 foo1() {
return new Foo1();
}
@Bean
public Foo2 foo2() {
Foo1 foo1 = foo1(); // 内部调用,依然从 Spring 容器中获取
return new Foo2(foo1);
}
}

@Component
public class Example {
@Autowired
private Foo1 foo1;
@Autowired
private Foo1 foo2;
@Autowired
private FullConfiguration fullConfiguration;

public void run() {
Assert.assertSame(foo1, foo2.getFoo1()); // = true
Assert.assertSame(foo1, fullConfiguration.foo1()); // = true
}
}

由于 @Bean 本身只是单纯的工厂方法,Spring 并不能阻止你拿到配置类以后直接调用方法创建新的对象,此时 Spring 容器将无法管理到以这种方式创建出的惰性。而 Full 模式可以避免这种问题,做到应管尽管。

当然,为配置类创建代理是需要额外的性能开销的,当项目中有非常多配置类时,就会比较影响启动速度。因此,在 Spring 5.2,@Configuration 新加了一个属性 proxyBeanMethods,它表示是否要代理被 @Bean 注解的工厂方法,默认为 true

当我们把 proxyBeanMethods 属性设置为 false 时,将不会对配置类和里面的方法进行代理,此时实际上就是 Lite 模式。

由于被代理方法需要满足 “支持内部调用”这个条件,因此 Full 模式的代理只能基于 CGLib 代理实现。关于 CGLib 代理可参见:✅ JDK 代理和 CGLib 代理的区别?

2. Lite 模式

只要不是在带有 @Configuration 注解(且注解的 proxyBeanMethods 属性为 true)的类中声明工厂方法,那都可以视为 Lite 模式。典型场景包括:在通过 @Import 引入的类中声明,或者在被 @Component 注解的类中声明。

Lite 模式与 Full 模式的特点基本相反的:

  • 是否代理:单纯作为配置类的时候,由于不需要增强,因此不需要进行 CGLib 代理对象。
  • 访问修饰符:类和带有 @Bean 注解的方法可以被 privatefinal 修饰(因为要被代理)。
  • 方法的调用结果:不管是外部类调用被 @Bean 注解的方法,还是类内部方法互相调用,这些方法返回的都是一个重新创建的对象。

你可以运行下面的代码感受一下:

@Component
public class FullConfiguration {
@Bean
public Foo1 foo1() {
return new Foo1();
}
@Bean
public Foo2 foo2() {
Foo1 foo1 = foo1(); // 内部调用,创建一个新的 Foo1
return new Foo2(foo1);
}
}

@Component
public class Example {
@Autowired
private Foo1 foo1;
@Autowired
private Foo1 foo2;
@Autowired
private FullConfiguration fullConfiguration;

public void run() {
Assert.assertSame(foo1, foo2.getFoo1()); // = false
Assert.assertSame(foo1, fullConfiguration.foo1()); // = false
}
}

当然,关于上述代理的部分需要打个补丁,如果你的配置类不是存粹的配置类,同时也是一个业务类,里头可能有些带有诸如 @Transcational 之类注解的方法,那该代理还会代理的,只不过走的就不是 ConfigurationClassEnhancer了。

这一部分的逻辑主要在 ConfigurationClassPostProcessor 处理器里面的 enhanceConfigurationClasses方法中,如果想要深入了解相关注解的生效逻辑,还是推荐结合文档直接阅读源码。