今天学习RabbitMQ的时候突然遇到了一个问题,这个问题其实是源于自己对于Spring的DI依赖注入掌握熟练度低而造成。下面给出一段代码。
@Component
public class DirectConfig {
@Bean
public DirectExchange directExchange() {
return ExchangeBuilder.directExchange("test.direct").build();
}
@Bean
public Queue queue1() {
return new Queue("direct.queue1");
}
@Bean
public Binding bindingQueue1WithRed(Queue queue1, DirectExchange exchange) {
return BindingBuilder.bind(queue1).to(exchange).with("red");
}
@Bean
public Binding bindingQueue1WithBlue(Queue queue1, DirectExchange exchange) {
return BindingBuilder.bind(queue1).to(exchange).with("blue");
}
@Bean
public Queue queue2() {
return new Queue("direct.queue2");
}
@Bean
public Binding bindingQueue2WithRed(Queue queue2, DirectExchange exchange) {
return BindingBuilder.bind(queue2).to(exchange).with("red");
}
@Bean
public Binding bindingQueue2WithYellow(Queue queue2, DirectExchange exchange) {
return BindingBuilder.bind(queue2).to(exchange).with("yellow");
}
}
个人疑惑:当让队列Queue1
和Queue2
和DirectExchange
进行绑定的时候,它是怎么正确绑定到对应的队列的,因为并没有通过@Bean注解参数
去重命名这个Bean
。那它是靠什么正确绑定的呢?
疑惑解答:其实这就是Spring的Spring IoC 容器的 Bean 名称匹配规则。
Bean 名称匹配规则
- 当方法参数需要注入
Queue
类型的 Bean 时,Spring 会按以下顺序查找:- 优先匹配参数名:
queue1
作为参数名称,对应@Bean
方法queue1()
定义的 Bean - 若参数名不匹配,则按类型匹配(此时若有多个同类型 Bean 会报错)
- 优先匹配参数名:
@Bean
public Queue queue1() { // Bean 名称默认为方法名 "queue1"
return new Queue("direct.queue1");
}
@Bean
public Queue queue2() { // Bean 名称默认为方法名 "queue2"
return new Queue("direct.queue2");
}
// 参数名 queue1 精准匹配 Bean 名称
@Bean
public Binding bindingQueue1WithRed(Queue queue1, DirectExchange exchange) {
return BindingBuilder.bind(queue1) // 此处 queue1 是名为 "queue1" 的 Bean
.to(exchange).with("red");
}
- 若参数名与 Bean 名称不匹配假设错误地写成:
@Bean
public Binding bindingError(Queue wrongName, DirectExchange exchange) {
return BindingBuilder.bind(wrongName)...
}
Spring 会抛出 NoSuchBeanDefinitionException
,因为容器中没有名为 wrongName
的 Queue
Bean。
Description:
Parameter 0 of method bindingQueue1WithRed in com.itheima.consumer.config.DirectConfig required a single bean, but 4 were found:
- queue1: defined by method 'queue1' in class path resource [com/itheima/consumer/config/DirectConfig.class]
- queue2: defined by method 'queue2' in class path resource [com/itheima/consumer/config/DirectConfig.class]
- fanoutQueue1: defined by method 'fanoutQueue1' in class path resource [com/itheima/consumer/config/FanoutConfig.class]
- fanoutQueue2: defined by method 'fanoutQueue2' in class path resource [com/itheima/consumer/config/FanoutConfig.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
Spring IoC 容器会在Bean注入或者实例完成会便会维护一张Bean映射表
Map<String, Object> beanRegistry = {
"queue1" : Queue("direct.queue1"), // 来自 queue1() 方法
"queue2" : Queue("direct.queue2"), // 来自 queue2() 方法
"directExchange" : DirectExchange("test.direct")
};
// 方法调用时的参数解析流程
MethodParameter param = getParameter("queue1"); // 参数名 queue1
Bean bean = beanRegistry.get(param.getParameterName()); // 查找名为 queue1 的 Bean
Bean注入的命名规则
通过上面的了解我们知道,Bean在注入的时候是通过名称进行匹配注入,然后是类型匹配。那现在又有一个问题,SpringIoc容器在每一个Bean注入的时候的命名规则是什么呢?
- 默认命名规则**
@Bean
方法名即 Bean 名称**
@Configuration
public class RabbitConfig {
@Bean
// Bean 名称 = 方法名 "directQueue"
public Queue directQueue() {
return new Queue("order.queue");
}
@Bean
// Bean 名称 = 方法名 "directExchange"
public DirectExchange directExchange() {
return new DirectExchange("order.exchange");
}
}
- 特殊字符处理,若方法名以
get
开头,Spring 会 自动去除前缀(遵循 JavaBean 规范)
@Bean
public Queue getPaymentQueue() {
// Bean 名称 = "paymentQueue"(而非 "getPaymentQueue")
return new Queue("payment.queue");
}
- 显式命名规则,通过
@Bean
的name
属性指定
@Bean(name = "priorityQueue") // 显式命名
public Queue createPriorityQueue() {
return new Queue("priority.queue");
}
也可以定义多个别名,如下免代码,可通过 emailQueue
或 mailQueue
引用该 Bean
@Bean({"emailQueue", "mailQueue"}) // 主名称 + 别名
public Queue emailQueue() {
return new Queue("email.queue");
}
注入时的名称解析规则
按名称注入的优先级
@Autowired
private Queue directQueue; // 变量名必须与 Bean 名称一致
@Bean
public Binding binding(
@Qualifier("directExchange") DirectExchange ex, // 显式指定名称
Queue directQueue // 参数名匹配 Bean 名称
) {
return BindingBuilder.bind(directQueue).to(ex).with("key");
}
多 Bean 冲突解决
当存在多个同类型 Bean 时,必须使用 @Qualifier
:
@Configuration
public class MultiQueueConfig {
@Bean
public Queue queueA() { return new Queue("q.A"); }
@Bean
public Queue queueB() { return new Queue("q.B"); }
}
@Service
public class OrderService {
@Autowired
@Qualifier("queueA") // 明确指定注入的 Bean 名称
private Queue targetQueue;
}
怎么查看Bean名称
编程方式打印所有 Bean
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(ConsumerApplication.class, args);
String[] beanNamesForType = run.getBeanNamesForType(Queue.class);
System.out.println("Queue Beans: " + Arrays.toString(beanNamesForType));
}
}
通过调试工具查看
在 IDEA 中,使用 Evaluate Expression 功能:
// 查看某个 Bean 的名称
ctx.getBeanNamesForType(Queue.class);
// 检查特定名称的 Bean 是否存在
ctx.containsBean("directQueue"); // 返回 true/false