限定符注解

学而不思则罔,思而不学则殆。

应用场景

  Spring使用自动装配将bean引用注入到构造参数和属性中时,仅有一个bean匹配所需的结果时,自动装配才是有效的,如果不止一个bean能够匹配,这种歧义性会阻碍Spring自动装配属性、构造器参数或方法参数,且会抛出NoUniqueBeanDefinitionException。

  如下有Computer:

package chapter3;

public interface Computer {
    void play();
}

  类AppleComputer,LenovoComputer,HaierComputer都实现了接口Computer,且均使用了@Component注解:

package chapter3;

import org.springframework.stereotype.Component;

@Component
public class AppleComputer implements Computer {

    public void play() {
        System.out.println("apple computer playing...");
    }
}
package chapter3;

import org.springframework.stereotype.Component;

@Component
public class LenovoComputer implements Computer {

    public void play() {
        System.out.println("lenovo computer playing...");
    }

}
package chapter3;

import org.springframework.stereotype.Component;

@Component
public class HaierComputer implements Computer {

    public void play() {
        System.out.println("haier computer playing...");
    }

}

  在Spring试图自动装配时,Spring无法做出选择,只好宣告失败并抛出异常:

package chapter3;

import org.springframework.stereotype.Component;

@Component
public class HaierComputer implements Computer {

    public void play() {
        System.out.println("haier computer playing...");
    }

}
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'chapter3.ComputerTest': 
Unsatisfied dependency expressed through method 'setComputer' parameter 0; nested exception is org.springframework.beans.
factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'chapter3.Computer' available: expected single matching
bean but found 3: appleComputer,haierComputer,lenovoComputer

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'chapter3.
Computer' available: expected single matching bean but found 3: appleComputer,haierComputer,lenovoComputer

  当遇到这种歧义性的时候,Spring提供了多种可选方案来解决这样的问题:设置首选的bean;使用限定符。

 

设定首选的bean

  在Spring中可以通过@Primary来指定首选的bean,@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中。

  如下使用@Component与@Primary组合指定AppleComputer为首选的bean,那么当创建Computer引用时,Spring会使用首选的bean。

package chapter3;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class AppleComputer implements Computer {

    public void play() {
        System.out.println("apple computer playing...");
    }
}
package chapter3;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
@ComponentScan
public class ComputerConfig {
    
    @Bean
    @Primary
    public Computer appleComputer() {
        return new AppleComputer();
    }
}

  在XML中使用primary属性指定首选的bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

   <bean id="appleComputer" class="chapter3.AppleComputer" primary="true"></bean>
</beans>

  @Primary无法指定唯一的、无歧义性的bean,如果你在LenovoComputer或HaierComputer类上也加上@Primary,那么也带来了新的歧义性问题。

 

使用限定符限定自动装配的bean

  @Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入的bean ID。

package chapter3;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ComputerConfig.class)
public class ComputerTest {
    
    private Computer computer;
    
    @Autowired
    @Qualifier("appleComputer")
    public void setComputer(Computer computer) {
        this.computer = computer;
    }
    
    @Test
    public void run() {
        computer.play();
    }
    
}

  因为@Qualifier限定符与要注入的bean的名称时紧耦合的,假设修改AppleComputer类的名称,那么限定符会失效,这个时候我们可以创建自定义的限定符,而不是依赖于将bean ID作为限定条件。

package chapter3;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("apple")
public class AppleComputer implements Computer {

    public void play() {
        System.out.println("apple computer playing...");
    }
}
package chapter3;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ComputerConfig.class)
public class ComputerTest {
    
    private Computer computer;
    
    @Autowired
    @Qualifier("apple")
    public void setComputer(Computer computer) {
        this.computer = computer;
    }
    
    @Test
    public void run() {
        computer.play();
    }
    
}

  还是存在一个问题,仅使用一个@Qualifier仍然无法确定唯一的、无歧义性的bean。

   Java 8 允许出现重复的注解,只要这个注解本身在定义时带有@Repeatable注解,但是@Qualifier注解在定义时没有该定义。

 * Copyright 2002-2011 the original author or authors.

package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation may be used on a field or parameter as a qualifier for
 * candidate beans when autowiring. It may also be used to annotate other
 * custom annotations that can then in turn be used as qualifiers.
 *
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @since 2.5
 * @see Autowired
 */
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {

    String value() default "";

}

  虽然我们无法只借助@Qualifier限定符注解就确定唯一、无歧义的bean,但是,我们可以创建自定义的限定符注解,然后进行任意组合直至将可选范围缩小到只有一个bean满需需求。

 

创建自定义的限定符注解

  创建@Apple限定符注解

package chapter3;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.annotation.Qualifier;

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Apple {

}

 

posted @ 2018-04-11 22:33  学而时习之,不亦说乎?  阅读(758)  评论(0编辑  收藏  举报