@Configuration注解

85 阅读4分钟

定义

@Configuration是一个类级注解,被标记的类的对象就是bean定义的来源(source)。@Configuration标记的类通过@Bean注解的方法来声明bean。对@Configuration类上的@Bean方法的调用也可以用于定义bean之间的依赖关系

注入bean之间的依赖

当beans之间存在依赖关系时,表达这种依赖关系只需让一个@bean的方法调用另一个@bean 的方法即可,如下例所示:

@Configuration
public class AppConfig {
//beanOne通过构造函数注入获得了对beanTwo的引用
	@Bean
	public BeanOne beanOne() {
		return new BeanOne(beanTwo());
	}

	@Bean
	public BeanTwo beanTwo() {
		return new BeanTwo();
	}
}

这种声明bean之间依赖关系的方法仅在@Bean方法声明于@Configuration标记的类中时有效。无法通过普通的@Component类来声明bean之间的依赖关系。当@Bean方法声明在没有被@Configuration注解的类中,或者声明在标注了@Configuration(proxyBeanMethods=false)的类中时,它们被称为在“轻量”(lite)模式下处理。在这种情况下,@Bean方法实际上是一种通用的工厂方法机制,没有特殊的运行时处理(即不会为其生成CGLIB子类)。对这类方法的普通Java调用不会被容器拦截,因此其行为就像常规方法调用一样,每次都会创建一个新实例,而不是为给定的bean重用现有的单例(或有作用域的)实例。

@Configuration内部工作机制

请考虑以下示例,该示例展示了一个带有@Bean注解的方法被调用两次的情况:

@Configuration
public class AppConfig {

	@Bean
	public ClientService clientService1() {
		ClientServiceImpl clientService = new ClientServiceImpl();
		clientService.setClientDao(clientDao());
		return clientService;
	}

	@Bean
	public ClientService clientService2() {
		ClientServiceImpl clientService = new ClientServiceImpl();
		clientService.setClientDao(clientDao());
		return clientService;
	}

	@Bean
	public ClientDao clientDao() {
		return new ClientDaoImpl();
	}
}

clientDao() 方法在 clientService1() 中被调用了一次,在 clientService2() 中也被调用了一次。由于该方法会创建 ClientDaoImpl 的新实例并返回,因此你通常会预期有两个实例(每个服务一个)。这确实会带来问题:在Spring中,实例化的bean默认具有单例作用域。这里就是魔法发生的地方:所有@Configuration标记类在启动时都会被 CGLIB进行子类化。在子类中,子方法会首先检查容器中是否存在已缓存(作用域)的 bean,然后再调用父方法并创建新实例。 这样保证了单例。

由于CGLIB会在启动时动态添加功能,因此存在一些限制。具体来说,配置类不能是final类。然而,配置类可以拥有任何构造函数,包括使用@Autowired注解或声明一个非默认构造函数以实现默认注入。

如果您希望避免 CGLIB 带来的任何限制,可以考虑将 @Bean 方法声明在非 @Configuration 类上(例如,声明在普通的 @Component 类上),或者通过在配置类上注解 @Configuration(proxyBeanMethods = false) 来实现。这样,@Bean 方法之间的跨方法调用将不会被拦截,因此您必须完全依赖于构造函数或方法级别的依赖注入。

单例bean依赖原型bean

Lookup方法注入是一种高级功能,应尽量少用。当单例作用域的Bean依赖于原型作用域的 Bean 时,此功能非常有用。

总结

  1. ​Full Mode (完整模式 - 默认且推荐):​​ @Configuration类 (默认proxyBeanMethods=true)。

    • ​行为:​​ Spring 代理配置类。@Bean方法间的调用被容器拦截,确保返回的是同一个Bean实例(单例)。
    • ​依赖声明:​​ 可以通过方法调用 (如 serviceA()里调用 serviceB()) 或方法参数注入。
    • ​优点:​​ 确保Bean间依赖的正确单例行为,代码写法更直观(类似普通Java调用)。
    • ​缺点:​​ 有少量CGLIB代理的运行时开销。
  2. ​Lite Mode (轻量模式):​​ @Bean方法在非@Configuration类中 或 @Configuration(proxyBeanMethods=false)类中。

    • ​行为:​​ 无代理。@Bean方法间的调用是普通Java调用,每次调用都创建新实例。
    • ​依赖声明:​​ ​​严禁​​使用方法调用声明依赖!必须通过@Bean方法参数注入依赖(或操作组件字段)。
    • ​优点:​​ 无CGLIB代理开销,启动更快,内存占用略少。
    • ​缺点:​​ 编程模型有约束(不能直接调用其他@Bean方法),容易误用导致非单例问题。