In Spring Cloud architecture projects, the configuration center is mainly used to provide distributed configuration management, with an important annotation: @refreshScope, which can be added to the required classes if the configuration needs to be dynamically refreshed in the code. This article to share the author met with @ ConditionalOnSingleCandidate notes the problem of conflict

The problem background

The project also introduced RabbitMQ and added @refreshScope when customizing the connectionFactory

@Bean @RefreshScope public CachingConnectionFactory connectionFactory() { CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); 172.17.0.111 connectionFactory. SetAddresses (" "); connectionFactory.setUsername("guest"); connectionFactory.setPassword("guest"); connectionFactory.setVirtualHost("/"); return connectionFactory; }Copy the code

The system fails to inject RabbitTemplate

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.pig4cloud.course.refresh.bug.RefreshBugApplicationTest':

Unsatisfied dependency expressed through field 'rabbitTemplate'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:

No qualifying bean of type 'org.springframework.amqp.rabbit.core.RabbitTemplate' available: expected at least 1 bean which qualifies as autowire candidate.

Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Copy the code

screening

    1. By default spring-boot-starter-AMqp will inject the rabbitTemplate implementation by default

RabbitAutoConfiguration#rabbitTemplate

@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnMissingBean(RabbitOperations.class)
public RabbitTemplate rabbitTemplate(RabbitTemplateConfigurer configurer, ConnectionFactory connectionFactory) {
  RabbitTemplate template = new RabbitTemplate();
  configurer.configure(template, connectionFactory);
  return template;
}Copy the code
    1. Start Spring Boot. Start the debugger logs to view the injection information
   RabbitAutoConfiguration.RabbitTemplateConfiguration#rabbitTemplate:
      Did not match:
         - @ConditionalOnSingleCandidate (types: org.springframework.amqp.rabbit.connection.ConnectionFactory; SearchStrategy: all)

         did not find a primary bean from beans 'connectionFactory', 'scopedTarget.connectionFactory' (OnBeanCondition)Copy the code

Prompt ConditionalOnSingleCandidate annotation method can’t find the only ConnectionFactory

    1. Beans that use the @refreshScope annotation are also generated by defaultscopedTarget.beanNameThe bean
@Autowired
private ApplicationContext context;

@Test
public void testRabbitTemplate() {
    String[] beanNames = context.getBeanNamesForType(ConnectionFactory.class);

    for (String beanName : beanNames) {
        System.out.println(beanName);
    }

    Assert.isTrue(beanNames.length == 2);
}

scopedTarget.connectionFactory
connectionFactoryCopy the code
    1. Because ConditionalOnSingleCandidate establishment is the condition of only one of this type of bean so the default RabbitTemplate cannot injection

Bean is common ConditionalOnSingleCandidate annotations

    1. Cannot add @refreshScope to the custom DataSource using JdbcTemplate
@ConditionalOnSingleCandidate(DataSource.class)
public class JdbcTemplateAutoConfiguration {}Copy the code
    1. MailSenderValidator Could not add @refreshScope to the mail send validator
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(MailSenderAutoConfiguration.class)
@ConditionalOnProperty(prefix = "spring.mail", value = "test-connection")
@ConditionalOnSingleCandidate(JavaMailSenderImpl.class)
public class MailSenderValidatorAutoConfiguration {}Copy the code
    1. Because there are many beans involved by default, these should be avoided when using RefreshScope

  • Supporting source: https://github.com/lltx/spring-boot-course/tree/master/v2.3/refresh-scope-bug