正文
基于默认的bean ID作为限定符是非常简单的,但这有可能会引入一些问题。如果你重构了IceCrean类,将其重名为“Gelato”的话,那此时会发生什么情况?如果是这样的话,bean的默认ID和默认的限定符会变为gelato,这就无法匹配setDessert()方法中的限定符,自动装配会失败。
这里的问题在于setDessert()方法上所指定的限定符与要注入的bean的名称是紧耦合的。对类名称的任意改动都会导致限定符失败。
我们可以为bean设置自己的限定符,而不是依赖于将ID作为限定符。在这里所需要做的就是在bean声明上加@Qualifier注解。
@Component
@Qualifier("cold")
public class IceCream implements Dessert {...}
在这种情况下,cold限定符分配了IceCream bean。因为它没耦合类名,因此你可以随意重构IceCream,而不必担心会破坏自动装配。
在注入的地方,只要引用cold限定符就可以了
@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
值得一提的是,当通过Java配置显式定义bean的时候i@Qualifier也可以与@Bean注解一起。
@Bean
@Qualifier
public Dessert dessert () {
return new IceCream();
}
当使用自定义的@Qualifier值时,最佳实践是为bean选择特征性或描述性的术语,而不是使用随意的名字。
面向特性的限定符要比基于bean ID的限定符更好一些。但是如果多个bean都具备这个相同的特性的话,这种做法也会出现问题。
这里只有一个小问题:
Java不允许在同一个条目上重复出现相同类型的多个注解。如果你试图这样做的话,编译器将会出错。
但是我们可以创建 自定义的注解,借助这样的注解来表达bean所希望限定的特性。这里需要做的就是创建一个注解,它本身要使用@Qualifier注解来标注。
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface cold {}
当你不想用 @Qualifier注解的时候,可以类似的创建@Sort、@Crispy和@Fruity。通过在定义时添加@Qualifier注解。它就具有了@Qualifier注解的特性。
现在我们重新看一下IceCream,并为其添加@Cold和@Creamy注解
@Component
@Clod
@Creamy
public class IceCream implements Dessert {...}
类似的,Popsicle类可以添加@Cold和@Fruity注解
@Component
@cold
@Fruity
public class Popsicle implements Dessert {...}
最终,在注入点,我们使用必要的限定符注解进行任意组合,从而将可选的范围缩小到只有一个bean满足需求。
为了得到IceCream bean 和 setDessert()方法可以这样使用注解:
@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会再有Java编译器的限制或错误,与此同时,相对于原始的@Qualifier并借助于String类型来指定限定符,自定义的注解也是类型安全的。
在本节和前节中,我们讨论了几种通过自定义注解扩展Spring的方式,
为了创建自定义的条件化注解,我们建议一个新的注解并在这个注解上添加了Conditional,为了创建自定义的限定符注解,我们创建一个新的注解并在这个注解上添加了@Qualifer。这种技术可以用到很多Spring注解中,从而能够将他们组合在一起形成特定目标的自定义注解。
3.4 bean的作用域
默认情况下,Spring应用上下文中所有的bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多次,每次所注入的都是同一个bean。
在大多数情况下,单例bean是很理想的方案,初始化和垃圾回收对象实例所带来的成本只有一些小规模任务。在这些任务中,让对象保持无状态并且在应用中反复使用这些对象可能并不合理。
有时候可能发现,你所使用的类是异变的(mutable),它们会保持一些状态,因此重复使用时不安全的。在这种情况下将class声明为单例的bean就不是什么好主意了。因为会污染对象,稍后重用的时候会出现意想不到的问题。
Spring定义了多种作用域可以基于这些作用域创建bean,包括:
-
单例(singleton):在整个应用中,只创建bean的一个实例
-
原型(prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例
-
会话(Session):在Web应用中,每个会话创建一个bean实例
-
请求(Request):在Web应用中,为每个请求创建一个bean实例。
单例是默认的作用域,但是正如之前所述,对于异变的类型,这并不适合。如果要选择其他作用域,要使用@Scope注解,它可以与@Component或@Bean一起使用。
如果你使用组件扫描来发现bean和生命bean,那么你可以在bean的类上使用@Scope注解,并将其声明为原型bean
@Component
@Scope