【Spring高级配置】
1.环境与profile
应用程序在不同的环境中可能会出现问题,比如开发环境和生产环境,测试环境等等。而DataSource最为明显,不同的环境获取DataSource的方式不同。
那么如何实现在不同的环境也可以正确运行,而且不需要重构呢?
可以配置 profile bean。
1.JavaConfig中可用@Profile注解来指定某个bean属于哪个profile,如下:
@Configuration
@Profile("dev")
public class DevelopmentProfileConfig{
@Bean(destroyMethod="shutdown")
public DataSource dataSource(){
.....
}
}
除了在类中使用profile,也可以在方法级别上使用profile,如下:
@Configuration
public class DevelopmentProfileConfig{
@Bean(destroyMethod="shutdown")
@Profile("dev")
public DataSource dataSource(){
.....
}
}
2.可以在XML中配置profile,如下:
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql"/>
<jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>
</beans>
需要激活profile才会执行相应的profile,spring确定profile是否激活主要依赖两个属性:
spring.profiles.active 和 spring.profiles.default
如果设置了spring.profiles.active则激活,如果没有,则去找spring.profiles.default
可以在web.xml文本中设置default profile.
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</pparam-value>
</comtext-param>
使用profile进行测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest{
......
}
2.条件化bean
Spring4推出@Conditional注解实现
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean(){
return new NagicBean();
}
@Conditional会默认实现了Condition接口的:
public interface Condition{
boolean matches(ConditionContext ctxt,AnnotatedTypeMetadata metadata);
}
public class MagicExistsCondition implements Condition{
//true才会创建带有@Conditional注解的Bean
public boolean matches(ConditionContext ctxt,AnnotatedTypeMetadata metadata){
Environment env = context.getEnvironment();
//检查环境中是否存在magic属性,不管这个属性的值是什么,存在即可
//设置了只有magic环境属性的时候,Spring才会实例化这个类
return env.containsProperty("magic");
}
}
ConditionContext 和 AnnotatedTypeMetadata 皆是接口,ConditionContext可以获取环境,而AnnotatedTypeMetadata可以检查带有@Bean注解的方法上还有什么注解。值得注意的是:从Spring4开始,@Profile注解进行了重构,使其基于@Conditional 和 Condition实现。
3.自动装配的歧义性
如果不仅仅有一个bean能够匹配结果的话就会带来歧义性。如:
@Autowired
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
@Component
public class Cake implements Dessert{......}
public class Cookies implements Dessert{......}
public class IceCream implements Dessert{......}
Spring试图装配setDessert()时,无法选择Dessert参数,会抛出NoUniqueBeanDefinitionException
解决方法:
1.标识首选的Bean
——用到@Primary注解,如下三种方式
@Component
@Primary
public class IceCream implements Dessert{......}
@Bean
@Primary
public Dessert iceCream(){
return new IceCream();
}
<bean id="iceCream" class="com .desserteater.IceCream" primary="true">
缺点:无法标识多个首选bean,标识多个首选bean相当于没有首选bean
2.限定自动装配的bean
@Qualifier注解可以和@Autowired 和 @Inject协同使用,如:
@Autowired
@Qualifier("iceCream(bean的ID)")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
使用@Qualifier注解时需要引用bean的ID作为限定符,如果没有定义,会默认把bean类名的第一个字母小写作为限定符,这样存在一个问题就是当你重构了bean,例如 重构了IceCream,将其重命名为Gelato,则bean的ID和默认限定符就会变为gelato,这就无法匹配到setDessert()方法中的限定符。
解决方法:
创建自定义限定符
在bean的声明上加上@Qualifier("自定义限定符")eg:
@Component
@Qualifier("cold")
public class IceCream implements Dessert{......}
4.bean的作用域
单例(Singleton):在整个应用中,只创建bean的一个实例,默认。在大多数情况下,单例bean是一个很理想的方案,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。但是当你所使用的类是易变的,这时重用就会不安全,再用到单例作用域就不合适。
原型(Protorype):每次注入或者通过Spring 应用上下文获取的时候,都会创建一个新的bean实例。
组件扫描的方式声明bean可以用@Scope注解,将其声明为原型bean:
@Component
@Scope(ConfigurableBeanFactory.SCEPE_PROTOTYPE)
public class Notepad{......}
当然,可以用@Scope("protorype")来声明原型bean,但使用SCOPE_PROTOTYPE常量更加安全并且不容易出错。
在JavaConfig中配置:
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad(){
return new Notepad();
}
在XML中配置:
<bean id="notepad" class="com.myapp.Notepad" scope="prototype" />
会话(Session):在web应用中,为每个会话创建一个bean实例。
eg:在典型的电子商务应用中,可能会有一个bean代表用户的购物车,如果用单例代表bean的话,那么将会导致所有的用户都会向同一个购物车中添加商品,如果用原型,那么在应用中某一个地方往购物车中添加商品,在应用的另一个地方可能就不可用了,因为在这里注入的是另一个原型的购物车。
@Component
//value值告诉Spring为WEB应用中的每个会话创建一个ShoppingCart
//proxyMode属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题
@Scope(value=WebApplicationContext.SCOPE_SESSION,proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){.......}
proxyMode是java代理模式
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
若不指明proxyMode,当把一个session或request的bean注入到sigleton的bean时,会出现问题。如把购物车bean注入到service bean
@Component public class StoreService { @Autowired public void setShoppingCart(ShoppingCart shoppingCart) { this.shoppingCart = shoppingCart; } ... }
因为StoreService是signleton,是在容器启动就会创建,而shoppingcart是session,只有用户访问时才会创建,所以当StoreService企图要注入shoppingcart时,很有可能shoppingcart还没创建。spring用代理解决这个问题,当ShoppingCart是接口时,指定 ScopedProxyMode.INTERFACES。当ShoppingCart是一个类时,则指定ScopedProxy- Mode.TARGET_CLASS,srping会通过CGLib来创建基于类的代理对象。当request注入到signleton bean时,也是一样。
proxyMode属性被设置成ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并调用委托给实现bean
在XML中指定代理模式:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" 3 http://www.springframework.org/schema/aop 4 http://www.springframework.org/schema/aop/spring-aop.xsd 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd"> 7 8 <bean id="cart" class="com.myapp.ShoppingCart" scope="session"> 9 <aop:scoped-proxy /> 10 </bean> 11 </beans><aop:scoped-proxy>与@Scope注解的proxyMode属性功能相同
5.运行时值注入
在笔记一中提到,将BlankDisc属性注入到compactDisc中时使用了硬编码格式:
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="第一个参数的值"/>
<constructor-arg value="第二个参数的值"/>
</bean>
<bean id="compactDisc" class="soundsystem.BlankDisc">
c:_title="参数title的值"
c:_artist="参数artist的值"/>
<bean id="compactDisc" class="soundsystem.BlankDisc">
c:_0="参数title的值"
c:_1="参数artist的值"/>
而避免硬编码Spring提供了两种在运行时求值的方式:
1.属性占位符Property placeholder
注入外部值:1.声明属性源@PropertySource; 2.根据Spring的Environment来检索属性
@Configuration
//声明属性源
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig{
@Autowired
Environment env;
@Bean
public BlankDisc disc(){
//检索属性值
return new BlankDisc(env.getProperty("disc.title"),env.getProperty("disc.artist"));
}
}
app.properties文件的内容大致如下:
disc.title=title的值
disc.artist=artist的值
当然,可以设置默认值,当所查的属性不存在时:
在JavaConfig中:
@Configuration
//声明属性源
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig{
@Autowired
Environment env;
@Bean
public BlankDisc disc(){
//检索属性值
return new BlankDisc(env.getProperty("disc.title","title默认值"),env.getProperty("disc.artist","artist默认值"));
}
}
在XML中可以为:
<bean id="sgtPeppers" class="soundsystem.BlankDisc"
c:_title="${disc.title}"
c:_artist="${disc.artist}" />
在组件扫描和自动装配中:
pubic BlankDisc(@Value("${disc.title}") String title, @Value("${disc.artist}") String artist){
this.title = title;
this.artist = artist;
}
为了使用占位符,必须要配置一个PropertySourcesPlaceholeerConfigurer bean ,如下:
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
如果要使用XML配置,Spring context命名空间中的<context:property-placeholder>元素将会为你生成PropertySourcesPlaceholderConfigurer bean :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:property-placeholder location="classpath:foo.properties"/>
</beans>
2.Spring表达式语言 SpEL(复杂但常用)
SpEL特性:
a.使用bean的ID来引用bean
b.调用方法和访问对象的属性
c.对值进行算术、关系和逻辑运算
d.正则表达式匹配
e.集合操作
属性占位符放到“${......}”中,而SpEL表达式要放到"#{......}"之中。eg:
#{1}:最简单的SqEL表达式.
#{T{System}.currentTimeMillis()}:T()表达式会将java.lang.System视为java中对应的类型,因此可以调用其static
修饰currentTimeMillis() 方法.
#{sgtPeppers.artist}:得到ID为sgtPeppers的bean的artist属性
#{systemProperties['disc.title']}:通过systemproperties对象引用系统属性
组件扫描和自动装配:
pubic BlankDisc(
@Value("#{systemProperties['disc.title']}") String title,
@Value("#{systemProperties['disc.title']}") String artist){
this.title = title;
this.artist = artist;
}
在XML配置中:
<bean id="sgtPeppers" class="soundsystem.BlankDisc"
c:_title="#{systemProperties['disc.title']}"
c:_artist="#{systemProperties['disc.artist']}"
/>