关于SpringIoc的Bean名称

今天学习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");
    }
}

个人疑惑:当让队列Queue1Queue2DirectExchange进行绑定的时候,它是怎么正确绑定到对应的队列的,因为并没有通过@Bean注解参数去重命名这个Bean。那它是靠什么正确绑定的呢?

疑惑解答:其实这就是Spring的Spring IoC 容器的 Bean 名称匹配规则

Bean 名称匹配规则

  • 当方法参数需要注入Queue类型的 Bean 时,Spring 会按以下顺序查找:
    1. 优先匹配参数名queue1 作为参数名称,对应 @Bean 方法 queue1() 定义的 Bean
    2. 若参数名不匹配,则按类型匹配(此时若有多个同类型 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,因为容器中没有名为 wrongNameQueue 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");
}
  • 显式命名规则,通过 @Beanname 属性指定
@Bean(name = "priorityQueue") // 显式命名
public Queue createPriorityQueue() {
    return new Queue("priority.queue");
}

也可以定义多个别名,如下免代码,可通过 emailQueuemailQueue 引用该 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值