Talk about @ qualifier in Spring dependency injection

Posted by taldos on Thu, 13 Jan 2022 06:22:22 +0100

Spring supports injecting dependencies of single type and collection type. For a single type, if you inject by type, spring will throw a NoUniqueBeanDefinitionException when there are multiple beans of the same type in the container. In this case, we can choose to set a bean as primary. However, if there are multiple primary beans, spring will still be unable to process them. At this time, we will introduce @ Qualifier, which can be used to specify which bean to inject.

@Use of Qualifier annotations

@Qualifier annotations are usually used in two ways.

  • When injecting a bean of a single type into a dependency, the name of the dependent bean is explicitly pointed out to avoid throwing exceptions when there are multiple beans of the same type.
  • Group dependencies when injecting a collection type.

An example of injecting a single type bean is as follows:

public class App {

    @Qualifier("bean1")	// ①
    @Autowired
    private String bean;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        context.register(App.class);

        context.refresh();

        System.out.println(context.getBean(App.class).bean);

        context.close();
    }

	// @Qualifier("bean1") ②
    @Bean
    public String bean1() {
        return "bean1";
    }

    @Bean
    public String bean2() {
        return "bean2";
    }

}

In the above example, two beans of type String are registered in the container. When injecting dependencies, use @ Qualifier to indicate that the name of the bean to be injected is bean1, so as to avoid throwing exceptions. Note that this is equivalent to adding @ Qualifier("bean1") to bean1, and the same purpose can be achieved by modifying position ① and position ② in the code to @ Qualifier("bean").

An example of using @ Qualifier to group dependencies for a collection type is as follows:

public class App {

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    @Qualifier
    public static @interface MyQualifierGroup{

    }

    @Autowired
    private List<String> bean12;

    @Qualifier
    @Autowired
    private List<String> bean34;

    @MyQualifierGroup
    @Autowired
    private List<String> bean56;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(App.class);
        context.refresh();
        App app = context.getBean(App.class);
        System.out.println("bean12: "+app.bean12);
        System.out.println("bean34: "+app.bean34);
        System.out.println("bean56: "+app.bean56);
        context.close();
    }

    @Bean
    public String bean1() {
        return "bean1";
    }

    @Bean
    public String bean2() {
        return "bean2";
    }

    @Qualifier
    @Bean
    public String bean3() {
        return "bean3";
    }

    @Qualifier
    @Bean
    public String bean4() {
        return "bean4";
    }

    @MyQualifierGroup
    @Bean
    public String bean5() {
        return "bean5";
    }

    @MyQualifierGroup
    @Bean
    public String bean6() {
        return "bean6";
    }
}

In the above example, six String type beans are registered in the Spring container. Bean1 and bean2 are not annotated with @ Qualifier, bean3 and bean4 are annotated with @ Qualifier, and bean5 and bean6 are annotated with @ MyQualifierGroup using @ Qualifier. At the same time, three list < String > type dependencies are injected into the bean whose class type is App, Do not add @ Qualifier, add @ Qualifier and add @ MyQualifierGroup annotation respectively. The printing results are as follows:

bean12: [bean1, bean2, bean3, bean4, bean5, bean6]
bean34: [bean3, bean4, bean5, bean6]
bean56: [bean5, bean6]

Without @ Qualifier annotation, all bean s of the required type are injected. After @ Qualifier annotation is added, the dependent annotation must be consistent with the @ Qualifier type specified by us before injection.

@Simple analysis of Qualifier implementation

@Qualifier is used as annotation and processed by the context of annotation processing. AnnotatedBeanDefinitionReader} will read the annotation information as BeanDefinition. The construction method of AnnotatedBeanDefinitionReader is as follows:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	Assert.notNull(environment, "Environment must not be null");
	this.registry = registry;
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
	// Registers the processor that handles annotations
	AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

When instantiating, AnnotatedBeanDefinitionReader will call AnnotationConfigUtils#registerAnnotationConfigProcessors to register some beanpostprocessors processing annotations with Spring. The tracking source code is as follows:

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
		BeanDefinitionRegistry registry, @Nullable Object source) {

	DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
	if (beanFactory != null) {
		if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
			beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
		}
		if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
			// Register auto injected candidate parser
			beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
		}
	}
	... Omit some codes
}

When registering the BeanPostProcessor, Spring will first register the automatically injected candidate resolver, contextannotation autowirecandidate resolver. The focus is on this resolver. When Spring resolves the dependency, it will call} DefaultListableBeanFactory#isAutowireCandidate to judge whether the bean of a given type is a dependency candidate. The tracking source code is as follows:

protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd,
		DependencyDescriptor descriptor, AutowireCandidateResolver resolver) {

	String beanDefinitionName = BeanFactoryUtils.transformedBeanName(beanName);
	resolveBeanClass(mbd, beanDefinitionName);
	if (mbd.isFactoryMethodUnique && mbd.factoryMethodToIntrospect == null) {
		new ConstructorResolver(this).resolveFactoryMethodIfPossible(mbd);
	}
	BeanDefinitionHolder holder = (beanName.equals(beanDefinitionName) ?
			this.mergedBeanDefinitionHolders.computeIfAbsent(beanName,
					key -> new BeanDefinitionHolder(mbd, beanName, getAliases(beanDefinitionName))) :
			new BeanDefinitionHolder(mbd, beanName, getAliases(beanDefinitionName)));
	// Resolve whether the given bean is a candidate for automatic injection
	return resolver.isAutowireCandidate(holder, descriptor);
}

The ContextAnnotationAutowireCandidateResolver set above is used here. This class will match the @ Qualifier on the bean with the @ Qualifier information in the dependency descriptor to group the beans.

 

Draught does not forget the well Digger:

 

Topics: Spring