带有@Qualifier 的@Bean 声明不起作用

Posted

技术标签:

【中文标题】带有@Qualifier 的@Bean 声明不起作用【英文标题】:@Bean declaration with @Qualifier doesn't work 【发布时间】:2018-07-20 19:22:29 【问题描述】:

假设我有一个配置类(JmsQueueConfig,见下文)。在这个类中,我想为我的整个应用程序配置多个队列。对于一个队列,没有问题。但是,当我添加第二个队列并尝试从服务 (MemberService) 中使用其中一个队列时,Spring-boot 会告诉我

构造函数的参数1 in com.example.notification.application.jms.JmsEventPublisher 需要一个 bean,但找到了 2 个: - queueAccountToNotification:由类路径资源中的“queueAccountToNotification”方法定义 [com/example/notification/application/jms/JmsQueueConfig.class] - queueNotificationToAccount:由类路径资源中的“queueNotificationToAccount”方法定义 [com/example/notification/application/jms/JmsQueueConfig.class]

行动:

考虑将其中一个 bean 标记为 @Primary,更新消费者 接受多个bean,或使用@Qualifier 来识别bean 应该吃掉的

这是我的配置类:

@Configuration
@EnableJms
@ImportAutoConfiguration(classes = 
        JmsAutoConfiguration.class,
        ActiveMQAutoConfiguration.class
)
public class JmsQueueConfig 

   @Value("$APP_QUEUE_ACCOUNT_TO_NOTIFICATION")
   private String queueAccountToNotificationName;

   @Value("$APP_QUEUE_NOTIFICATION_TO_ACCOUNT")
   private String queueNotificationNameToAccount;

   @Bean
   @Qualifier("q1")
   public Queue queueAccountToNotification() 
      return new ActiveMQQueue(queueAccountToNotificationName);
   

   @Bean
   @Qualifier("q2")
   public Queue queueNotificationToAccount() 
      return new ActiveMQQueue(queueNotificationNameToAccount);
   

   @Bean
   public MessageConverter jacksonJmsMessageConverter() 
      MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
      converter.setTargetType(MessageType.TEXT);
      converter.setTypeIdPropertyName("_type");
      return converter;
   

   @Bean
   @Qualifier("p1")
   public EventPublisher eventPublisher(JmsTemplate jmsTemplate) 
      return new JmsEventPublisher(jmsTemplate, new ActiveMQQueue(queueAccountToNotificationName));
   

   @Bean
   public MessageConverter messageConverter() 
      return new JmsMessageConverter();
   

我的服务:

@Service
@FieldDefaults(level = AccessLevel.PRIVATE)
@AllArgsConstructor
@Slf4j
public class MemberService 

   @Autowired
   @Qualifier("q1")
   Queue q;

   @Qualifier("p1")
   EventPublisher eventPublisher;

   public void createMemberSubscription(final Member member) 
      final MembershipSubscriptionEvent event = new MembershipSubscriptionEvent(UUID.randomUUID().toString(), member.getEmail());
      //eventPublisher.publish(event);
      log.info("createMemberSubscription");
   

   public void removeMemberSubscription(final Member member) 
      final MembershipRemovalEvent event = new MembershipRemovalEvent(UUID.randomUUID().toString());
      //eventPublisher.publish(event);
      log.info("removeMemberSubscription");
   

我是 Spring 生态系统的新手,我可能不太了解 @Autowired 和绑定。任何好的文档或示例将不胜感激。 在 Spring 和 SoF 上,我还没有找到任何此类文档。

更新: JmsEventPublisher 类

@Component
@FieldDefaults(level = AccessLevel.PRIVATE)
@Slf4j
@AllArgsConstructor
public class JmsEventPublisher implements EventPublisher 

   final JmsTemplate jmsTemplate;
   final Destination destination;

   @Override
   public void publish(DomainEvent event) 
      jmsTemplate.convertAndSend(destination, event);
      log.trace("Sent event. [destination=, event=]", destination, event);
   

【问题讨论】:

JmsEventPublisher 是什么,它是您的自定义类吗?如果可以,您也可以输入该代码 给你(在更新的部分下) 【参考方案1】:

我想你误解了@Qualifier 从文档中,“此注释可用于字段或参数,作为自动装配时候选 bean 的限定符。

在你的情况下,@Qualifier 没有意义。

@Bean
   @Qualifier("q1")
   public Queue queueAccountToNotification() 
      return new ActiveMQQueue(queueAccountToNotificationName);
   

相反,你应该这样做

@Bean(name = "q1")
   public Queue queueAccountToNotification() 
      return new ActiveMQQueue(queueAccountToNotificationName);



@Bean(name = "q2")
   public Queue queueNotificationToAccount() 
      return new ActiveMQQueue(queueNotificationNameToAccount);
   

同样删除eventPublisher(...)上的@Qualifier

这并不能解决所有问题。 :)

如异常所示,spring 无法自动装配 JmsEventPublisher 中的 Destination 字段。因为它有两个 Destination(q1 和 q2) 类型的 bean。 要解决这个问题,您可以做的是。

@Primary 放在bean 声明之一上,然后使用@Qualifier

@Primary
@Bean(name = "q1")
   public Queue queueAccountToNotification() 
      return new ActiveMQQueue(queueAccountToNotificationName);



public class JmsEventPublisher implements EventPublisher 

   final JmsTemplate jmsTemplate;
   @Qualifier("q1")
   final Destination destination;

     ..........

 

底线是@Qualifier要在多个相同类型的bean的情况下工作,你需要把@Primary

另一个选项是不使用@Primary,您可以将变量完全命名为 Bean 名称,然后 spring 会自动为您注入正确的 bean。即

public class JmsEventPublisher implements EventPublisher 

  final JmsTemplate jmsTemplate;
  final Destination q1; // q1 or q2
  .....

在会员服务中类似

public class MemberService 

  @Autowired
  Queue q1; // q1 or q2
  .....


【讨论】:

你是对的,我误解了@Qualifier。您的示例非常适合理解。 @ekemchitsiga 请详细说明 @pvpkiran 检查我发布的答案【参考方案2】:

要更正接受的答案,您对@Qualifier 使用的理解是正确的。它可以在两种情况下使用。它可以与 @Bean 配置方法一起使用,为 bean 提供限定符。如果未提供,则默认为 bean 名称。

它也可以用于注入目标,即带有@Autowired 或@Inject 注解的方法或字段。在这种情况下,如果找到多个 bean,它会帮助 Spring 自动装配基础设施根据限定符(通过 @Bean 方法提供)过滤匹配注入目标的 bean 候选

错误的原因是由于

@AllArgsConstructor
public class JmsEventPublisher implements EventPublisher

@AllArgsConstructor 将生成以下构造函数

public JmsEventPublisher(JmsTemplate jmsTemplate, Destination destination)
   //body
 

Spring 将尝试通过构造函数自动装配 JmsEventPublisher,因为它有一个不是无参数构造函数的构造函数。然而,Destination 类型的参数匹配 Queue 类型的两个 bean。

解决方案是使用显式构造函数。即删除@AllArgsConstructor并定义constrctor如下

public JmsEventPublisher(JmsTemplate jmsTemplate, @Qualifier("q1")Destination destination)
   //body
 

或使用字段或 setter 注入,即删除 @AllArgsConstructor 并注入字段或 setter 方法

public class JmsEventPublisher implements EventPublisher 

   private JmsTemplate jmsTemplate;
   @Qualifier("q1")
   private Destination destination;

 

【讨论】:

使用@Bean @Qualifier"name" 代替@Bean("name") 有什么好处? 这是当前给定问题的好答案。 Lombok AllArgConstructor 没有利用字段限定符注释。【参考方案3】:

final Destination destination 更改为final Destination q1,它应该可以工作。我有同样的问题,它对我有用。

【讨论】:

以上是关于带有@Qualifier 的@Bean 声明不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Spring处理自动装配的歧义性

Spring中@Autowired@Qualifier@Resource的区别

@Qualifier 注解有什么用?

@Qualifier 注解有什么用?

@Autowired 凝视遇到的问题,@Qualifier 帮助解决这个问题

@Qualifier注解详解