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); } }
日志打印结果为: