1、 Spring环境
实际的开发过程中,会有不同的环境存在,如测试环境、生产环境等。
针对不同的环境,需要根据当前需要来判断某些bean是否需要创建,因此,Spring在3.1版本中引入了bean profile功能。
1.1 配置profile bean
在Java配置中,有两种方式来设置bean的profile。
- 通过
@Profile
注解指定摸个bean属于哪一个profile; - 通过在XML中配置
<beans>
元素的profile
属性;
第一种方式的示例如下:
@Configuration
@Profile("dev")
public class StudentConfig {
@Bean
public Student oneStudent() {
return new Student();
}
}
此处的@Profile("dev")
应用在了类级别上,它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。
@Bean
@Profile("dev")
public Student oneStudent() {
return new Student();
}
相对的,@Profile
注解也可以应用在方法级别上,表示当前bean只有在dev profile激活时才会创建。
第二种方式的示例如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
profile="dev"
>
通过设置根<beans>
元素的profile
属性来配置环境,只有profile属性与当前激活profile相匹配的配置文件才会被用到。
此外,还可以采用在根<beans>
元素中嵌套定义<beans>
元素,将所有的profile bean定义放到同一个XML文件
中,如下:
<beans profile="dev">
<bean>
...
</bean>
</beans>
<beans profile="qa">
<bean>
...
</bean>
</beans>
<beans profile="prod">
<bean>
...
</bean>
</beans>
这种形式,可以将所有的bean定义到同一个XML文件中,在运行时,会根据当前的激活状态来决定创建bean。
1.2 激活profile
Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active
和spring.profiles.default
。
(1)如果设置了spring.profiles.active
属性,设置的值就会用来确定哪个profile是激活的。
(2)如果没有设置spring.profiles.active
属性,就会查找spring.proflies.default
的值。
(3)如果两个都没有设置,则没有激活的profile,只会创建那些没有定义在profile中的bean。
有多种方式来设置这两个属性:
- 作为
DispatcherServlet
的初始化参数; - 作为Web应用的上下文参数;
- 作为JNDI条目;
- 作为环境变量;
- 作为JVM的系统属性;
- 在集成测试类上,使用
@ActiveProfiles
注解设置;
2、 条件化的bean声明
在某些情况下,需要某个bean在满足前置条件下才创建。
Spring 4中引入了@Conditional
注解,可以应用在带有@Bean
注解的方法上,只有在给定条件的结果为true,该bean才会创建,否则该bean就会被忽略。
@Bean
@Conditional(StudentExistCondition.class)
public Student oneStudent() {
return new Student();
}
如上,该Student类,只有在满足条件的情况下,才会被实例化。
@Conditional
中给定了一个class,指明了条件。
StudentExistCondition.class
如下:
public class StudentExistCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
return environment.containsProperty("dev");
}
}
该类实现了Condition接口,并实现了接口中的matches方法,当matches()
方法返回true时,所有@Conditional
注解上引用StudentExistCondition
的bean就会被创建。
3、 自动装配与歧义性
在使用@Autowired
注解,若存在多个可匹配的结果时,这种歧义性会阻碍Spring自动装配属性、构造器参数或方法参数。
例如,如下有一个方法setOwner()
。
@Autowired
public void setOwner(People people) {
this.people = people;
}
而People
是一个接口,有若干个实现类,如下:
@Component
public class Student implements People{ ... }
@Component
public class Teacher implements People{ ... }
由于这两个实现类都使用了@Component
注解,在组件扫描中,能够发现它们并将其创建为Spring应用上下文里面的bean。当Spring自动装配setOwner()
中的People参数时,造成没有唯一无歧义的可选值,导致抛出异常。
Spring提供了如下方案来解决。
3.1 标示首选的bean
在声明bean的时候,将其中一个可选的bean设置为primary,能够避免自动装配时的歧义性。Spring会在遇到歧义性时使用首选的bean。
(1)@Primary
与@Component
组合用在组件扫描的bean上。
@Component
@Primary
public class Student implements People{ ... }
(2)@Primary
与@Bean
组合用在Java配置的bean声明中。
@Bean
@Primary
public Peolple Student(){
return new Student();
}
3.2 使用限定符限定自动装配
使用限定符的方式是在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限制条件。
通过使用@Qualifier
注解,可以与@Autowired
和@Inject
协同使用。
@Autowired
@Qualifier("student")
public void setOwner(People people){
this.people = people;
}
其中,为@Qualifier
注解所设置的参数就是要注入的bean的ID。
注:使用限定符装配,其所指定的限定符与要注入的bean的名称是紧耦合的,对类名称的任意改动都会导致限定符失效。
3.2.1 自定义限定符
此外,也可以给bean设置自己的限定符,而不依赖于bean ID作为限定符。
通过在bean声明上添加@Qualifier
注解,如下:
@Component
@Qualifier("boyStudent")
public class Student implements People{ ... }
同时,在需要注入的地方,只要引用自己设定的限定符,此处为“boyStudent”,如下:
(1)与@Autowired
一起使用
@Autowired
@Qualifier("boyStudent")
public void setOwner(People people){
this.people = people;
}
(2)与@Bean
一起使用
@Bean
@Qualifier("boyStudent")
public People Student(){
return new Student();
}
3.2.2 使用自定义的限定符注解
假设有多个bean使用了相同的限定符,也会产生歧义,而Java不允许在一个条目上出现多个相同类型的注解。
因此,可以通过创建自定义的限定符注解来解决这个问题。
做法:将如上的@Qualifier("bodyStudent")
替换为自定义的@BoyStudent
注解。
注解的定义如下所示:
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface BoyStudent{ }
通过定义时添加@Qualifier
注解,就具有了@Qualifier
注解的特性。
这样,上例中就可以将@Qualifier("bodyStudent")
替换成@BoyStudent
,其作用相同。
@Autowired
@BoyStudent
public void setOwner(People people){
this.people = people;
}
通过自定义的限定符注解,可以对必要的限定符进行任意组合,进一步缩小可选范围。
4、 bean的作用域
Spring中默认bean是单例的。
除此之外,Spring定义了多种作用域,如下所示:
- 单例(Singleton):在整个应用中,值创建bean的一个实例;
- 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例;
- 会话(Session):在Web应用中,为每个会话创建一个bean实例;
- 请求(Request):在Web应用中,为每个请求创建一个bean实例;
如果选择其他的作用域,要使用@Scope
注解,它可以与@Component
和@Bean
一起使用。
4.1 单例作用域
Spring中默认是单例作用域。
4.2 原型作用域
例如:将bean声明为原型bean:
(1)使用@Scope("prototype")注解
@Component
@Scope("prototype")
public class Notepad{ ... }
(2)使用@Scope("ConfigurableBeanFactory.SCOPE_PROTOTYPE")
注解
@Component
@Scope("ConfigurableBeanFactory.SCOPE_PROTOTYPE")
public class Notepad{ ... }
4.3 会话与请求作用域
在Web应用中,在会话范围内共享bean,即用到会话作用域。
常见场景:在电商项目中,有一个bean表示用户购物车,若采用单例作用域,则不同用户在添加商品时都会将商品添加到同一个购物车中;若采用原型作用域,则用户在某一个地方向购物车添加商品,在另一个地方则不可用。
对于购物车bean,采用会话作用域,可以将该实例与给定的用户相关联。
设置会话作用域如下:
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopeProxyMode.INTERFACES)
public ShopCar cart(){ ... }
此处,将value
设置成WebApplicationContext中的SCOPE_SESSION常量(值为session)。告诉Spring为Web应用中的每个会话创建一个ShopCar。
相当于在当前会话中,bean相当于单例的。
另一个proxyMode
属性,设置成ScopeProxyMode.INTERFACES
。该属性解决了将会话作用域及请求作用域的bean注入到单例bean中会遇到的问题。
适用场景:如下有一个单例的StoreService
bean的Setter方法,需要将ShopCar bean注入。
@Component
public class StoreService{
@Autowired
public void setShopCar(ShopCar shopCar){
this.shopCar = shopCar;
}
}
由于StoreService是单例bean,在Spring应用上下文加载的时候创建。当创建时,Spring会将ShopCar的bean注入到setShopCar()
方法中,但由于ShopCar bean是会话作用域的,此时不存在,只有在创建了会话之后才会有ShopCar实例。
如配置,proxyMode
属性被设置成ScopedProxyMode.INTERFACES
,Spring会注入一个到ShopCar bean的代理。