Spring in action读书笔记(四) Spring 处理自动装配的歧义性
1、 有歧义的bean
自动装配中仅有一个bean匹配所需要的结果时,自动装配才是有效的。如果有多个bean能够匹配结果的话,
这种奇异性会阻碍Spring自动装配属性,构造器参数或方法参数。
假设定义了一个接口。
package test; public interface Interface { }
有两个实现类:
package test; import org.springframework.stereotype.Component; @Component public class Impl1 implements Interface { }
package test; import org.springframework.stereotype.Component; @Component public class Impl2 implements Interface { }
创建配置类InterfaceConfig
package test; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackageClasses = Interface.class) public class InterfaceConfig { }
这个时候如果注入Interface属性,程序将报错。
package test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {InterfaceConfig.class}) public class TestAutowired { @Autowired private Interface imp; @Test public void test() { System.out.println(imp); } }
错误信息:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'test.TestAutowired': Unsatisfied dependency expressed through field 'imp'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'test.Interface' available: expected single matching bean but found 2: impl1,impl2
大致是说产生了歧义性,无法确定装配哪一个bean
2、 使用@Primary注解,标记首选的bean
package test; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component @Primary public class Impl1 implements Interface { }
缺点: 如果标记了两个或更多的首选bean,仍然会产生歧义性。
3、在需要注入参数的地方,使用限定符@Qualifier注解,指定装配bean 的ID
package test; 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 = {InterfaceConfig.class}) public class TestAutowired { @Autowired @Qualifier("impl2") private Interface imp; @Test public void test() { System.out.println(imp); } }
缺点: 有类型强耦合,对类名的改动会导致限定符失效
4、在bean上使用@Qualifier设定限定符,在注入的地方使用相同的限定符。
package test; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component @Qualifier("1") public class Impl1 implements Interface { }
注入:
package test; 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 = {InterfaceConfig.class}) public class TestAutowired { @Autowired @Qualifier("1") private Interface imp; @Test public void test() { System.out.println(imp); } }
不足: 如果多个bean都具备相同特性,会再次遇到歧义性的问题。
例如:对Impl2增加同样的注解,这时会产生和步骤1中同样的问题;
package test; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component @Qualifier("1") public class Impl2 implements Interface { }
由于@Qualifier注解不可重复, 这时没有直接的方法将自动装配的bean缩小范围到仅有一个可选的bean
5、自定义限定符注解
package test; import org.springframework.beans.factory.annotation.Qualifier; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Interface1 { }
在bean类及需要注入的地方添加该注解:
package test; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component @Interface1 public class Impl1 implements Interface { }
package test; 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 = {InterfaceConfig.class}) public class TestAutowired { @Autowired @Interface1 private Interface imp; @Test public void test() { System.out.println(imp); } }
可以通过多个自定义注解,缩减满足bean装配的范围。