跳到主要内容

使用 @Configuration 注解

DeepSeek V3 中英对照 Using the @Configuration annotation Using the @Configuration annotation

@Configuration 是一个类级别的注解,表示一个对象是 bean 定义的来源。@Configuration 类通过 @Bean 注解的方法来声明 bean。在 @Configuration 类上调用 @Bean 方法也可以用于定义 bean 之间的依赖关系。有关一般介绍,请参阅基本概念:@Bean 和 @Configuration

注入 Bean 之间的依赖

当 beans 之间存在依赖关系时,表达这种依赖关系就像让一个 bean 方法调用另一个 bean 方法一样简单,如下例所示:

@Configuration
public class AppConfig {

@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}

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

在前面的示例中,beanOne 通过构造函数注入接收了对 beanTwo 的引用。

备注

这种声明 bean 之间依赖关系的方法仅在 @Bean 方法声明在 @Configuration 类中时有效。你不能通过使用普通的 @Component 类来声明 bean 之间的依赖关系。

查找方法注入

如前所述,查找方法注入 是一种高级功能,你应该很少使用它。它在单例作用域的 bean 依赖于原型作用域的 bean 的情况下非常有用。使用 Java 进行这种类型的配置为实现这种模式提供了一种自然的方式。以下示例展示了如何使用查找方法注入:

public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}

// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
java

通过使用 Java 配置,你可以创建一个 CommandManager 的子类,在该子类中重写抽象的 createCommand() 方法,使其能够查找一个新的(原型)命令对象。以下示例展示了如何实现这一点:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}

@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
java

关于基于 Java 的配置内部工作原理的更多信息

考虑以下示例,其中显示了一个被 @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();
}
}
java

clientDao()clientService1()clientService2() 中各被调用了一次。由于该方法会创建一个新的 ClientDaoImpl 实例并返回它,通常情况下你会期望有两个实例(每个服务一个)。这显然会带来问题:在 Spring 中,实例化的 bean 默认具有 singleton 作用域。这就是魔法所在:所有 @Configuration 类在启动时都会通过 CGLIB 进行子类化。在子类中,子方法在调用父方法并创建新实例之前,会首先检查容器中是否有任何缓存的(作用域内的)bean。

备注

根据你的 bean 的作用域,行为可能会有所不同。我们在这里讨论的是单例。

备注

无需将 CGLIB 添加到你的类路径中,因为 CGLIB 类已经重新打包在 org.springframework.cglib 包下,并直接包含在 spring-core JAR 中。

提示

由于 CGLIB 在启动时动态添加功能,因此存在一些限制。特别是,配置类不能是 final 的。然而,配置类上允许使用任何构造函数,包括使用 @Autowired 或声明一个非默认构造函数以实现默认注入。

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