3.1.1 配置profile bean
使用@profile 注解
例如 @Configuration
@Profile("dev")
public class xxxConfig{
@Bean
public XXX xx(){
return xxx;
}
}
@Profile注解应用在了类级别上。 它会告诉
Spring这个配置类中的bean只有在dev profile激活时才会创建。 如果
dev profile没有激活的话, 那么带有@Bean注解的方法都会被忽略
掉。
从Spring3.2开始,profile注解可以使用在方法级别上。
未声明在一个给定profile范围内的bean始终都会被创建,与激活哪个profile没有关系
在XML中配置profile
通过<beans>元素的 profile属性来在XML中配置profile bean。
可以通过在根元素<beans>里嵌套<beans>元素来将所有的profile bean定义放到一个XML文件中。
3.1.2 激活profile
spring.profiles.active的值来设置哪个profile被激活,
如果没有设置的话,spring将查找spring.profiles.default的值。如果都未设置,那就没有
激活的profile,因此只会创建那些没有定义在profile的bean。
有多种方式来设置这两个属性:
作为DispatcherServlet的初始化参数;
作为Web应用的上下文参数;
作为JNDI条目;
作为环境变量;
作为JVM的系统属性;
在集成测试类上, 使用@ActiveProfiles注解设置。
<context-param>
<param-name>spring.profiles.default </param-name>
<param-value>dev</param-value>
</context-param>
<servlet>
<servlet-name>```
````
<init-param>
<param-name>spring.profiles.default </param-name>
<param-value>dev</param-value>
</init-param>
在集成测试类上, 使用@ActiveProfiles注解设置。
@ActiveProfiles("dev")
3.2条件化的bean
@Conditional(XXClassExistCondition.class)
XXClassExistCondition是任意实现了Condition接口的类
public interface Condition(){
boolean matches(COnditionContext ctxt,AnnotatedTypeMetadata metadata);
}
如果matches返回true,将会创建带有@Conditional注解的bean,如果返回为false,将
不会创建这些bean。
3.3 处理自动装配的歧义行
@Autowired
public void serDessert(Dessert dessert){
this.dessert=dessert;
}
Dessert是一个接口,有cake,cookies,Icecream 3个类实现了这个接口
@Conponent
public class Cake implements Dessert(){}
@Conponent
····
当Spring自动为setDessert装配Dessert参数时,没有唯一、无歧义的可选值,
所以会抛出NoUniqueBeanDefinitionExcepetion。
此时可以将bean中的某一个设为首选(primary)的bean,或者使用限定符(qualifier)来帮助
spring缩小范围。
3.3.1 标识首选的bean
@Conponent
@Primary
Java配置
@Bean
@Primary
XML配置
<bean id="xxx"
class="xx"
primary="true"
/bean>
如果你标识了多个首选bean,那么就无法正常工作。
3.3.2限定自动装配的bean
@Autowired
@Qualifier("icecream") //bean 默认ID为首字母变小写的类名 Icecream->icecream
public void serDessert(Dessert dessert){
this.dessert=dessert;
}
创建自定义的限定符
@Conponent
@Qualifier("自定义限定符")
当通过Java配置显式定义bean的时候, @Qualifier
也可以与@Bean注解一起使用
@Bean
@Qualifier("自定义限定符")
Java不允许在同一个条目上重复出现相同类型
的多个注解
所以我们可以创建自定义的限定符注解,这里我们创建一个注解,它本身用Qualifier注解来标注,这 样我们就不再使用@Qualifier("自定义限定符"),而是直接使用自定义的注解@自定义限定符
@Qualifier
public @interface 自定义限定符
@Conponent
@自定义限定符
最终, 在注入点, 我们使用必要的限定符注解进行任意组合, 从而将
可选范围缩小到只有一个bean满足需求。
默认情况下, Spring应用上下文中所有bean都是作为以单例(singleton) 的形式创建的,也就是说一个bean被注入到其他bean多少次都是同一个实例。
Spring定义了多种作用域, 可以基于这些作用域创建bean, 包括:
单例(Singleton) : 在整个应用中, 只创建bean的一个实例。
原型(Prototype) : 每次注入或者通过Spring应用上下文获取的
时候, 都会创建一个新的bean实例。
会话(Session) : 在Web应用中, 为每个会话创建一个bean实
例。
请求(Rquest) : 在Web应用中, 为每个请求创建一个bean实
例。
单例是默认的作用域, 但是正如之前所述, 对于易变的类型, 这并不
合适。 如果选择其他的作用域, 要使用
@Scope注解
@Scope ConfigurableBeanFactory.SCOPE_PROTOTYPE或者@Scope ("prototype")。
在Java配置中与@Bean同时使用时同理。
XML配置
<bean id="xx"
class="xxx"
scope="prototype"/>
3.4.1 使用会话和请求作用域
购物车bean来说, 会话作用域是最为合适的
@Conponent
@Scope( value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
public ShopipingcCart cart(){
}
Spring为Web应用中的每个会话创建一个ShoppingCart。
这会创建多个ShoppingCart bean的实例, 但是对于给定的会话只会创建一个
实例, 在当前会话相关的操作中, 这个bean实际上相当于单例的。
@Scope同时还有一个proxyMode属性, 它被设置成了ScopedProxyMode.INTERFACES。 这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。
假设我们要将ShoppingCart bean注入到单例StoreService bean
的Setter方法中
@Conponent
public class ShoppingService{
public void setshoppingcart(ShoppingCart shoppingcart){
this.shoppingcart=shopingcart;
}
···
}
StoreService是一个单例bean,会在Spring应用上下文加载
的时候创建。 当它创建的时候, Spring会试图将ShoppingCart bean
注入到setShoppingCart()方法中。 但是ShoppingCart bean是
会话作用域的, 此时并不存在。 直到某个用户进入系统, 创建了会话
之后, 才会出现ShoppingCart实例
系统中将会有多个ShoppingCart实例: 每个用户一个。 我
们并不想让Spring注入某个固定的ShoppingCart实例
到StoreService中。 我们希望的是当StoreService处理购物车
功能时, 它所使用的ShoppingCart实例恰好是当前会话所对应的
那一个。
Spring并不会将实际的ShoppingCart bean注入到StoreService中,
Spring会注入一个到ShoppingCart bean的代理, 如图3.1所示。 这
个代理会暴露与ShoppingCart相同的方法, 所以StoreService
会认为它就是一个购物车。 但是, 当StoreService调
用ShoppingCart的方法时, 代理会对其进行懒解析并将调用委托
给会话作用域内真正的ShoppingCart bean。
proxyMode属性被设置成了
ScopedProxyMode.INTERFACES, 这表明这个代理要实现
ShoppingCart接口, 并将调用委托给实现bean
如果ShoppingCart是接口而不是类的话, 这是可以的(也是最为
理想的代理模式) 。 但如果ShoppingCart是一个具体的类的话,
Spring就没有办法创建基于接口的代理了。 此时, 它必须使用CGLib
来生成基于类的代理。 所以, 如果bean类型是具体类的话, 我们必须
要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS, 以此来表明要以生成目标类扩展的方式创建代理。
3.4.2 在XML中声明作用域代理
<bean id="cart"
class="com.myapp.ShoppingCart"
scope="session"
<aop:scoped-proxy />
</bean>
默认情况下, 它会使用CGLib创建目标类的代理。
但是我们也可
以将proxy-target-class属性设置为false, 进而要求它生成基
于接口的代理:<aop:scoped-proxy proxy-target-class="false" />
为了使用<aop:scoped-proxy>元素, 我们必须在XML配置中声明
Spring的aop命名空间:
xmlns:aop="http://www.springframework.org/schema/aop"
3.5.1 注入外部的值
在Spring中, 处理外部值的最简单方式就是声明属性源并通过Spring
的Environment来检索属性
int connetctioncount = env.getProperty("db.Connnetction.count",Integer.class,30)
如果你在使
用getProperty()方法的时候没有指定默认值, 并且这个属性没有定义的话, 获取到的值是null。 如果你希望这个属性必须要定义, 那么可以使用getRequiredProperty()方法
在此时,如果disc.title 或者disc.artist属性没有定义的话,将会抛出异常。
如果想检查一下某个属性是否存在的话,那么可以调用Environment的containsProperty()方法,boolean titleExists = env.containsProperty("dist.title")
最后, 如果想将属性解析为类的话, 可以使
用getPropertyAsClass()方法:
占位符的使用形式为${···}
依赖于组件扫描和自动装配来创建和初始化应用组件的话,
那么就没有指定占位符的配置文件或类了。 在这种情况下, 我们可以
使用@Value注解, 它的使用方式与@Autowired注解非常相似。 比
如, 在BlankDisc类中, 构造器可以改成如下所示:
为了使用占位符, 我们必须要配置一
个PropertyPlaceholderConfigurer bean
或PropertySourcesPlaceholderConfigurer bean。 从Spring
3.1开始, 推荐使
用PropertySourcesPlaceholderConfigurer, 因为它能够基
于Spring Environment及其属性源来解析占位符。
如下的@Bean方法在Java中配置了
PropertySourcesPlaceholderConfigurer:
如果你想使用XML配置的话, Spring context命名空间中的
<context:propertyplaceholder>元素将会为你生
成PropertySourcesPlaceholderConfigurer bean
SpEL表达式 格式:#{···}
例:#{T(System).currentTimeMillis()}
T()表达式将java.lang.System 视为java中对应的类型,因此可以条用其static修饰的currentTimeMillis()方法
SpEL也可以引用其他bean或其他bean的属性
如下的表达式会计算得到ID为sgtPeppers的bean的artist属性
#{sgtPeppers.artist}
还可以通过systemProperties对象引用系统属性
#{systemProperties[‘disc.title‘]}
让我们看一下在bean装配的时候如何使用这些表达式,与占位符非常相似
在XML中
SpEL支持的基础表达式
1)表示字面值
#{3.1415}
#{"hello"}
字面值true和false的计算结果就是它们对应的Boolean类型的值。
#{false}
引用bean、 属性和方法
你可以使用SpEL将一个bean装配到另外一个bean的属性中, 此时要使用bean ID作为SpEL表达式
#{sgtPeppers}
假设我们想在一个表达式中引用sgtPeppers的artist属
性: #{sgtPeppers.artist}
我们还可以调用bean上的方法
#{artistSelector.selectArtist().toUpperCase()}
如果selectArtist()的返回值不是null的话, 这没有什么问题。
为了避免出现NullPointerException, 我们可以使用类型安全的
运算符:
与之前只是使用点号(.) 来访问toUpperCase()方法不同, 现在我们使用了“?.”运算符。 这个运算符能够在访问它右边的内容之前, 确保它所对应的元素不是null。 所以, 如果selectArtist()的返回值是null的话, 那么SpEL将不会调用toUpperCase()方法。 表达式的返回值会是null。
在表达式中使用类型
如果要在SpEL中访问类作用域的方法和常量的话, 要依赖T()这个关键的运算符。 例如, 为了SpE中表达Java的Math类, 需要按照如下的方式使用T()运算符:
T(java.lang.Math)
这里所示的T()运算符的结果会是一个Class对象, 代表了java.lang.Math。
T()运算符的真正价值在于它能够访问目标类型的静态方法和常量
SpEL还提供了三元运算符(ternary)
三元运算符的一个常见场景就是检查null值
计算正则表达式
计算集合
SpEL还提供了查询运算符(.?[])
SpEL还提供了另外两个查询运算符: “.^[]”和“.$[]”, 它们分别用来在集合中查询第一个匹配项和最一个匹配项。 例如, 考虑下面的表达式, 它会查找列表中第一个artist属性为Aerosmith的歌曲
SpEL还提供了投影运算符(.![]) , 它会从集合的每个成员中选择特定的属性放到另外一个集合中。 作为样例, 假设我们不想要歌曲对象的集合, 而是所有歌曲名称的集合。 如下的表达式会将title属性投影到一个新的String类型的集合中:
投影操作可以与其他任意的SpEL运算符一起使用。 比如,我们可以使用如下的表达式获得Aerosmith所有歌曲的名称列表: