Spring 高级装配
标签: Spring
1.处理自动装配时的歧义性
例子:
下面的代码通过注解的方式声明了三个组件分别实现接口Dessert,而setDessert()
方法通过@Autowired
自动装配。
@Autowired
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
@Componenet
public class Cake implememnts Dessert{...}
@Componenet
public class Cookies implememnts Dessert{...}
@Componenet
public class IceCream implememnts Dessert{...}
组件扫描时,Spring将抛出异常,NoUniqueBeanDefinitionException
。
1.1标示首先的Bean
像下面这样:
@Componenet
@Primary
public class Cake implememnts Dessert{...}
<!-- 通过XML声明IceCream为首选项 -->
<bean id="iceCream" class="IceCream" primary="true"/>
1.2创建自定义的限定符
@Autowired
@Qualifier("cold")//这里的Qualifier表明自动注入时Bean的限定符
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
@Component
@Qualifier("cold")//这里的Qualifier表明组件的限定符
public class IceCream implements Dessert {
}
这里某个Bean的限定符不一定是单一的。可以是任意多个。
@Qualifier("cold")
@Qualifier("creamy")
1.3使用自定义的限定注解
@Retention(RUNTIME)
@Target({ TYPE, FIELD, METHOD, CONSTRUCTOR })
@Qualifier
public @interface Cold {
}
2.Bean的作用域
在默认情况下,Spring应用上下文中所有的Bean都是作为单例(Singleton)的形式创建的。也就是说,不管给定的Bean被注入到其他Bean多少次,每次所注入的都是一个实例。
Spring定义了多种作用域,可以基于这些作用域创建Bean,包括:
Bean | 解释 |
---|---|
单例Singleton | 在整个应用中,只创建Bean的一个实例 |
原型Prototype | 每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的Bean实例 |
会话Seesion | 在Web应用中,为每个会话创建一个Bean实例 |
请求Request | 在Web应用中,为每个请求创建一个Bean实例 |
依旧是两种配置方式:基于注解的和基于XML文件的。
- 基于注解的
使用@Scope
注解
@Component
@Scope(scopeName=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Cookies implements Dessert {
}
- 基于XML文件的
使用Bean的scope属性
<bean id="car2" class="test.Car"
p:name="Ford" p:speed="250" p:price="12340000.00"
scope="prototype"/>
2.1使用会话和请求作用
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMod=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() {...}
@Component
public class StoreService {
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
...
}
因为StoreService
是一个单例的Bean
,会在Spring
应用上下文加载的时候创建。当它创建的时候,Spring
会试图将ShoppingCart Bean
注入到setShoppingCart
方法中,但是ShoppingCart Bean
是会话作用域,此时并不存在。
系统中将会有多个ShoppingCart
实例:每个会话一个。我们并不像让Spring注入某个固定或者随机的ShoppingCart
实例到StoreService
中,我们希望注入的恰好是当前会话对应的那个。
Spring
并不会将实际的ShoppingCart Bean
注入到StoreService
中,Spring
会注入一个到ShoppingCart Bean
的代理。这个代理会暴露与ShoppingCart
相同的方法。当StoreService
调用ShoppingCart
的方法时,代理会对其进行懒解析并将调用委托给回话作用域内真正的ShoppingCart Bean
。
类型 | proxyMod | 补充 |
---|---|---|
接口 | INTERFACES | |
类 | TARGET_CLASS | 使用CGLib来生成基于类的代理 |
请求作用域应该也以作用域代理的方式注入
2.2在XML文件中声明作用域代理
要设置代理模式,需要使用Spring aop
命名空间的一个新元素。
<bean id="car3" class="test.Car" sco pe="session">
<aop:scoped-proxy/>
</bean>
设置<aop:scoped-proxy/>
下的proxy-target-class
为false
,进而要求它生成基于接口的代理
<bean id="wang" class="test.Person" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
3.运行时注入
Spring提供了两种在运行时求值的方式:
- 属性占位符
- Spring表达式语言(SpEL)
3.1注入外部的值
@Configuration
@PropertySource("classpath:car.propertites")
public class ExpressiveConfig {
@Autowired
Environment env;
@Bean
public Car getCar() {
return new Car(//检索属性值
env.getProperty("car.name"),
env.getProperty("car.speed", Integer.class),
env.getProperty("car.price", Double.class));
}
}
3.2了解Spring的Environment
- String getProperty(String key)
- String getProperty(String key, String defaultValue)
- T getProperty(String key, Class type)
- T getProperty(String key, Class type, T defaultValue)
如果某个属性没有定义,则返回null
,这时,指定默认值就起作用了。Environment
还可以检查某个属性是否存在,哪些profile处于激活状态以及将某个属性解析为类。
3.3解析属性占位符
在Spring装配中,占位符的形式为使用${...}
包裹的属性名称。
<bean id="carXML" class="test.Car">
<property name="name" value="${car.name}"></property>
<property name="speed" value="${car.speed}"></property>
<property name="price" value="${car.price}"></property>
</bean>
附上:配置文件car.propertites
里的内容
car.name=wanghao
car.speed=250
car.price=12345.00
3.4使用Spring表达式语言进行装配
Spring 3引入了Spring表达式语言,它够以一种强大和简洁的方式将值装配到Bean属性和构造器参数中,在这个过程中使用表达式会在运行时计算得到值。
- 表示字面值
用来表示浮点值,字符串,bool值。
#{3.1415}
#{9.87E4}
#{'Hello'}
#{false}
- 引用Bean,属性和方法
?.
运算符能够在在访问它右边的内容时,确保左边的不是null
。如果左边的内容时null
,它就不会调用右边的内容。
#{car}//引用Bean
#{car.speed}//引用属性
#{carSelector.selectCar()?.toUpperCase()}//引用方法
- 在表达式使用类型
T()表示括号里的是静态内容。
#{T(java.lang.Math).PI}
#{T(java.lang.Math).random()}
- SpEL运算符
用来操作表达式值得SpEL运算符
运算符类型 | 运算符 |
---|---|
算数运算 | + - * / $ ^ |
比较运算 | < > == <= >= lt gt eq le ge |
逻辑运算 | and or not ] |
条件运算 | ?: |
正则运算 | mathes |
- 计算集合
操作符[]
在集合操作中很强大,来几个例子感受一下。
#{jukebox.songs[3].title}
#{'hello world'[3]}
#{jukebox.songs.?[artist eq 'WangHao']}
对集合过滤
#{jukebox.songs.^[artist eq 'WangHao']}
在集合中查询第一个匹配项
#{jukebox.songs.$[artist eq 'WangHao']}
在集合中查询最后一个匹配项
#{jukebox.songs.![title]}
从集合中选出特定的属性放到另一个集合中
综合例子:
<bean id="spel" class="test.ManyProperty">
<property name="age" value="#{23}"></property>
<property name="name" value="#{'Wang' + 'Hao'}"></property>
<property name="price" value="#{123.45D}"></property>
<property name="man" value="#{true}"></property>
<property name="car" value="#{car1}"></property>
<property name="height" value="#{T(java.lang.Math).PI * 12}"></property>
<!--
这些多用于后者用于文本等
eq:等于
le ge:less equal==>小于等于 大于等于
lt gt:less than==>小于 大于
-->
<property name="testMany" value="#{12 le 12}"></property>
</bean>