@Configuration 是一个类级注解,表示一个对象是 Bean 定义的来源。@Configuration 类通过 @Bean 注解的方法声明 bean。对 @Configuration 类上的 @Bean 方法的调用也可以用来定义 Bean 的依赖关系。参见基本概念 @Bean 和 @Configuration 的介绍。
注入 bean 之间的依赖
当 Bean 相互之间有依赖关系时,表达这种依赖关系就像让一个 Bean 方法调用另一个一样简单,正如下面的例子所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
在前面的例子中,BeanOne 通过构造函数注入收到了对 BeanTwo 的引用。
:::info 这种声明 bean 之间依赖关系的方法只有在 @Configuration 类中声明了 @Bean 方法时才有效。你不能通过使用普通的 @Component 类来声明 bean 间的依赖关系。 :::
查找方法注入
如前所述,查找方法注入 是一个高级功能,你应该很少使用。在单例作用域的 Bean 对多例作用域的 Bean 有依赖性的情况下,它很有用。为这种类型的配置使用 Java 提供了实现这种模式的自然手段。下面的例子展示了如何使用查找方法注入:
public abstract class CommandManager {
public Object process(Object commandState) {
// 创建一个 command 接口的新的实例
Command command = createCommand();
// 希望在全新的实例对象上设置状态
command.setState(commandState);
return command.execute();
}
// 那么这个抽象方法的实现在哪里呢?
protected abstract Command createCommand();
}
通过使用 Java 配置,你可以创建一个 CommandManager 的子类,其中抽象的 createCommand() 方法被重载,这样它就可以查找到一个新的(多例)命令对象。下面的例子显示了如何做到这一点:
一个抽象类
package cn.mrcode.study.springdocsread.web;
/**
* @author mrcode
*/
public abstract class CommandManager {
public Command process(Object commandState) {
// 创建 Command 的实例
Command command = createCommand();
return command;
}
// 这个方法的实现在哪里呢?
protected abstract Command createCommand();
}
public class Command {
}
使用配置方式实例化,提供方法注入的效果
package cn.mrcode.study.springdocsread.web;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* @author mrcode
*/
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// 根据需要在这里注入依赖性
return command;
}
@Bean
public CommandManager commandManager() {
// 用 createCommand() 返回 CommandManager 的新匿名实现。
// 重载以返回一个新的 多例的 Command 对象
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
};
}
}
package cn.mrcode.study.springdocsread.web;
/**
* @author mrcode
*/
public class AsyncCommand extends Command{
}
:::tips
不使用这个 AsyncCommand 直接创建 Command 也是一样的;
它这种方式使用匿名 内部类的方式共享了 bean 方法的代理效果。其实 CommandManager 的 createCommand() 方法被我们自己实现了,不过是在 @Configuration 类中调用的 @Bean 方法,参数的多例
:::
有关基于 Java 的配置如何在内部工作的进一步信息
考虑下面的例子,它显示了一个 @Bean 注解的 clientDao 方法被调用了两次:
@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,然后再调用父方法并创建一个新实例。
:::info 根据你的 Bean 的范围,其行为可能是不同的。我们在这里讨论的是单例。 ::: :::info 从 Spring 3.2 开始,不再需要将 CGLIB 添加到你的 classpath 中,因为 CGLIB 类已经被重新打包到org.springframework.cglib 下,并直接包含在 spring-core JAR 中。 ::: :::tips 由于 CGLIB 在启动时动态地添加功能,所以有一些限制。特别是,配置类不能是 final 的。然而,从 4.3 开始,配置类允许任何构造函数,包括使用 @Autowired 或单一的非默认构造函数声明进行默认注入。
如果你想避免任何 CGLIB 施加的限制,可以考虑在非 @Configuration 类中声明你的 @Bean 方法(例如,在普通的 @Component 类中)。这样,@Bean 方法之间的跨方法调用就不会被拦截,所以你必须完全依赖构造函数或方法层面的依赖注入。 :::