spring自动装配的歧义性和解决办法

我们知道,spring的依赖注入有三种实现方式,分别是自动装配、java配置装配、xml文件装配。

其中自动装配是spring框架自动寻找符合条件的实例然后进行注入的。这里就可能会出现一个问题,即如果一个接口有多个实现类,则符合条件的实例就不止一个,那么spring容器就不知道应该选择哪一个了,这就是自动注入的歧义性。

 

spring为了解决自动注入的歧义性,给出了一个解决方案:使用注解@Primary指定我们中意的实现类。

假设接口F有三个实现类,分别是A、B、C,为了解决自动装配的歧义性,我们可以在A实现类上使用注解@Primary,代码如下:

package com.zaoren.bean;

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

/**
 * 读者
 */
@Primary
@Component
public class DuZhe implements ReadingMatter {

}

用@Primary注解标注之后,虽然符合类型标准的实例有三个,但是spring容器最终会选择A实现类的实例进行注入,因为A实现类被注解@primary标注了。

------------------------

这个方案虽然可行,但是却并不理想。我们在阅读代码的时候,为了知道到底使用的是哪一个类的实例,不得不把所有的实现类都阅读一遍,分辨出哪一个实现类使用了@Primary注解,很显然这样做非常麻烦。

于是spring给出了第二种解决方案,即使用@Qualifier注解进行限定筛选。

spring容器在实例化时,给每一个实例生成了一个ID,其取值方式为将类名的首字母变为小写的。@Qualifier的默认值为实例的ID。

使用@Qualifier注解进行筛选,我们需要在接口的所有实现类上使用@Qualifier注解,并且在定义属性时,通过@Qualifier注解指定使用哪一个实现类的实例,具体代码如下:

package com.zaoren.bean;
/**
 * 读物
 * @author thinkpad
 *
 */
public interface ReadingMatter {

}

 

package com.zaoren.bean;

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

/**
 * 读者
 * @author thinkpad
 *
 */
@Primary
@Component
@Qualifier
public class DuZhe implements ReadingMatter {

}
package com.zaoren.bean;

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

/**
 * 意林
 * @author thinkpad
 *
 */
@Component
@Qualifier
public class YiLin implements ReadingMatter {

}
package com.zaoren.bean;

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

/**
 * 故事会
 * @author thinkpad
 *
 */
@Component
@Primary
@Qualifier
public class GuShiHui implements ReadingMatter {

}

controller的代码为:

package com.zaoren.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.zaoren.bean.ReadingMatter;

@RequestMapping("user3")
@Controller
public class UserController3 {
    
    @Autowired
    @Qualifier("duZhe")
    private ReadingMatter readingMatter;

    @RequestMapping("test")
    public void test() {
        System.out.println("readingMatter = "+this.readingMatter);
    }
}

访问controller的test方法,输出的结果为:

readingMatter = com.zaoren.bean.DuZhe@7b24b40

可见,容器装配了我们指定的实现类的实例。

------------------------------------

上面的例子中,接口的实现类的@Qualifier注解的值都是默认的,即为实例的ID。这种耦合其实是不好的,因为如果我们重构代码时改变了实现类的名称,就还需要同步修改属性的注解的值。所以我们应该消除这种耦合,方法是为实现类的@Qualifier注解指定独立的值。这样做后,即使修改了实现类的名称,也不需要修改注解的值,达到了松散耦合的目的。取值的原则是用事物的特点来指代,因为类名可以变,但是事物的特定不会变。代码如下:

package com.zaoren.bean;

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

/**
 * 读者
 * @author thinkpad
 *
 */
@Primary
@Component
@Qualifier("philosophyBook")
public class DuZhe implements ReadingMatter {

}
package com.zaoren.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.zaoren.bean.ReadingMatter;

@RequestMapping("user3")
@Controller
public class UserController3 {
    
    @Autowired
    @Qualifier("philosophyBook")
    private ReadingMatter readingMatter;

    @RequestMapping("test")
    public void test() {
        System.out.println("readingMatter = "+this.readingMatter);
    }
}

日志打印结果为:

 

posted on 2019-10-16 10:08  星辰划过指尖  阅读(296)  评论(0编辑  收藏  举报