您的位置:首页 > 编程语言 > Java开发

Spring journey -- 处理自动装配歧义性问题

2017-03-04 19:58 381 查看
当需要把一个 Bean 装配到另一个 Bean 中时,一般都是给传一个接口,但是如果这个接口有多个实现类的话,Spring 就会不知道应该把哪一个实现类传过去,这个时候就会出现歧义性,像下面的这段代码:

Dessert 是一个接口:

package com.springinaction.dessert;

/**
* Created by user on 3/4/17.
*/
public interface Dessert {
}

它的三个实现类:

Cake:

package com.springinaction.dessert;

import org.springframework.stereotype.Component;

/**
* Created by user on 3/4/17.
*/
@Component
public class Cake implements Dessert {
}

Cookie:

package com.springinaction.dessert;

import org.springframework.stereotype.Component;

/**
* Created by user on 3/4/17.
*/
@Component
public class Cookie implements Dessert {
}


IceCream:

package com.springinaction.dessert;

import org.springframework.stereotype.Component;

/**
* Created by user on 3/4/17.
*/
@Component()
public class IceCream implements Dessert {
}

写一个测试类:

package com.springinaction.dessert;

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;

import static org.junit.Assert.assertNotNull;

/**
* Created by user on 3/4/17.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DessertConfig.class)
public class DessertTest {

@Autowired
private Cookie cookie;

@Test
public void cdShouldNotBeNull(){
assertNotNull(cookie);
}
}

运行之后的结果就是这样:



解决办法:可以给三个当中任意一个实现加一个@Primary 注解,用来确定维一性。但是如果给了两个实现上都加了@Primary 呢,结果是必然的,因为这是肯定不是唯一了撒。

@Qualifier 是使用限定符的主要方式,用来缩小选择范围。像下面这样,是不会报错的(此时@Primary 已经不在使用了):

package com.springinaction.dessert;

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

/**
* Created by user on 3/4/17.
*/
@Component
public class HandleQualifier {

private Dessert dessert;

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
}

从上面的代码可以看到@Qualifier 里指定了『iceCream』,这个名字是IceCream 类在组件扫描时自动创建的,也就默认的名字,但是如果后面对这个类进行重构后,名字发生了改变,这里肯定就会报错了,自动装配就会失败。即 setDessert()上所指定的限定符与要注入的 bean 的名称是紧耦合的。对类名称的任意改动都会导致限定符失效。就像下面这样,我把 IceCream 的名字改成这样:

package com.springinaction.dessert;

import org.springframework.stereotype.Component;

/**
* Created by user on 3/4/17.
*/
@Component("aa")
public class IceCream implements Dessert {
}

上面这里已经改了 IceCream 的名字,再去运行上面的测试代码时就会报错了,解决方法是要把 setDessert() 的 @Qualifier 改成下面这样:

package com.springinaction.dessert;

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

/**
* Created by user on 3/4/17.
*/
@Component
public class HandleQualifier {

private Dessert dessert;

@Autowired
@Qualifier("aa")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
}

所以这里就充分的说明了 setDessert()上面的限定符与要注入的 bean 的名称是紧耦合的关系。

那么解决办法是创建自定义的限定符,我们可以了 bean 设置自己的限定符,而不是依赖于将 beanID 作为出限定符。

现在把 IceCream 类改成这样:

package com.springinaction.dessert;

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

/**
* Created by user on 3/4/17.
*/
@Component()
@Qualifier("cold")
public class IceCream implements Dessert {
}

那么在 setDessert()方法上面就可以这样写了:

package com.springinaction.dessert;

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

/**
* Created by user on 3/4/17.
*/
@Component
public class HandleQualifier {

private Dessert dessert;

@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
}

面向特性的限定符要比基于 bean ID 的限定符更好一些。但是这个时候如果多个 bean 出现相同的特性的话,那上面的这种方法又会不能唯一确定到一个指定的 bean 了。如果你想再 setDessert()上面加多个限定符,情理之中,但是 Java 不允许在同一个条目上重复出现相同类型的多个注解,解决办法:创建自定义的限定符注解

自定义注解@Cold:

package com.springinaction.dessert;

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;

/**
* Created by user on 3/4/17.
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}

再来一个:

package com.springinaction.dessert;

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;

/**
* Created by user on 3/4/17.
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy {
}

现在把这两个注解添加到 IceCream 上面:

package com.springinaction.dessert;

import org.springframework.stereotype.Component;

/**
* Created by user on 3/4/17.
*/
@Component()
@Cold
@Creamy
public class IceCream implements Dessert {
}

为了得到 IceCream bean , 在 setDessert()上面就应该这样写了:

package com.springinaction.dessert;

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

/**
* Created by user on 3/4/17.
*/
@Component
public class HandleQualifier {

private Dessert dessert;

@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
}

现在回过头来看 setDessert 方法以及它上面的注解,可以发现没有任何地方明确指定要把 IceCream 自动装配到该方法中,所以 setDessert()方法可以与特定的 bean 保持解耦关系。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring