环境与profile
配置profile bean
- 在开发过程中,每个阶段的环境的配置有所不同,需要为每种环境重新构建应用。而通过Spring配置profile bean提供了解决方案
- 通过注解
@Profile
来指定某一个bean属于哪一个profile,例如@Profile("dev")
属于开发环境的profile,此bean只会在dev
profile激活时才会创建 @Profile
可以和@Bean
一同使用- 也可以通过xml配置文件来配置profile,例如
<beans profile="dev"></beans>
激活profile
- 需要依赖两个独立的属性:
spring.profiles.active
和spring.profiles.default
- 设置
spring.profiles.active
,会用来确定那个profile是激活的 - 没有设置
spring.profiles.active
,则会寻找spring.profiles.default
- 两者均没设置,则没有激活的profile
激活方式
- 作为web应用的上下文参数
- 作为DispatcherServlet的初始化参数
- 作为JNDI条目
- 作为环境变量
- 作为JVM的系统属性
- 在集成测试类上,使用@ActiveProfiles注解配置
示例:(使用DispatcherServlet参数设置)
<!-- 为上下文设置默认的profile --> <context-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </context-param> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 为DispatcherServlet设置默认的profile --> <init-param> <param-name>spring.profiles.default</param-name> <param-value>development</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
条件化的bean
通过注解@Conditional
来实现
@Configuration
public class MagicConfig {
@Bean
@Conditional(MagicExistsCondition.class) // 条件化创建bean
public static MagicBean magicBean() {
return new MagicBean();
}
}
设置给
@Conditional
的类可以是任意实现了Condition接口的类型public interfact Condition { boolean matches(ConditonContext ctxt, AnnotatedTypeMetadata metadata) }
- 只需要实现
matches
方法即可,返回true则创建bean,否则不会创建
- 只需要实现
Condition接口实现示例
public class MagicExistsCondition implements Condition{ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment env = context.getEnvironment(); return env.containsProperty("magic"); //检查magic属性 } }
- 通过给定的
ConditionContext
对象获得Environment
对象,并且检查此环境中是否存在名为magic的环境属性 @Profile
本身使用了@Conditional
注解,并且引用ProfileConditon作为Condition的实现
- 通过给定的
处理自动装配的歧义性
当多个类是实现了统一接口,而在装配此接口时,Spring没有唯一的可选值,会抛出异常
标示首选的bean
使用
@Primary
注解,与@Component
或者@Bean
等结合使用// 使用Component @Component @Primary public class IceCream implements Dessert {...} // 使用Bean @Bean @Primary public class IceCream implements Dessert {...}
- 或者在xml中
<bean>
标签的primary="true"
属性来配置 - 但多个类被标注为
@Paimary
时,装配仍然会出现异常
限定自动装配的bean
- 在装配时使用
@Qualifier
限定符
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert
}
创建自定义限定符
- 在
@Component
,@Bean
等注解后添加自定义的@Qualifier
在装配时,即可使用自定义的限定符
// 使用Component,使用Bean注解类似 @Component @Qualifier("cold") public class IceCream implements Dessert {...}
@Autowired @Qualifier("cold") public void setDessert(Dessert dessert) { this.dessert = dessert }
- 当多个bean使用同一个
@Qualifier
时,依然会出现冲突 可以通过再添加一个
Qualifier
解决// 使用Component,使用Bean注解类似 @Component @Qualifier("cold") @Qualifier("creamy") public class IceCream implements Dessert {...}
@Autowired @Qualifier("cold") @Qualifier("creamy") public void setDessert(Dessert dessert) { this.dessert = dessert }
bean的作用域
默认情况下bean都是单例的形式创建的
Spring定义了多种作用域
- 单例(Singleton): 在整个应用中,只会创建bean的一个实例
- 原型(Prototype): 每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例
- 会话(Session): 在Web应用中,为每个会话创建一个bean实例
- 请求(Request): 在Web应用中,为每个请求创建一个bean实例
- 通过
@Scope
注解来定义作用域
使用会话和请求作用域
例如电商中的购物车: 如果为单例,则所有用户共享同一购物车;如果为多例,则每个商品都放在不同的购物车里。因此会话作用域更加适合
- 定义会话作用域:
@Component @Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES) public class ShoppingCart {...}
value
的值为WebApplicationContext.SCOPE_SESSION
即会话proxyMode解决问题的场景
@Component public class StoreService { @Autowired public void setShoppingCart(ShoppingCart shoppingCart) {...} }
- StoreService是一个单例的bean,在Spring应用上下文加载的时候创建。当其创建后,用户并未发起会话,因此并不存在bean。而多个用户使用时,我们希望StoreService只在处理购物车功能时,使用当前的
ShoppingCart
bean实例 - Spring并不会将实际的
ShoppingCart
bean注入到StoreService中,而是注入一个到ShoppingCart
bean的代理。这个代理会暴露和ShoppingCart
相同的方法,但是当StoreService调用时,代理会将其解析并且调用委托给会话作用域内真正的ShoppingCart
bean - 因此
proxyMode
属性被设置为ScopedProxyMode.INTERFACES
表明代理需要实现ShoppingCart
接口,并将调用委托给实例bean,此方法为最理想的代理模式 - 如果
ShoppingCart
是类的话,Spring将无法创建基于接口的代理,则必须使用CGLib来生成基于类的代理,而proxyMode
属性值为ScopedProxyMode.TARGET_CLASS
- StoreService是一个单例的bean,在Spring应用上下文加载的时候创建。当其创建后,用户并未发起会话,因此并不存在bean。而多个用户使用时,我们希望StoreService只在处理购物车功能时,使用当前的
使用请求作用域是也同样应该通过作用域代理的方式进行注入
在XML中声明作用域代理
<bean id="example" class="com.eric.exmaple.Example" scpoe="session">
<aop:scoped-proxy proxy-target-class="false">
</bean>
- 在
<bean>
中的scope
属性可以选择作用域,通过<aop:scoped-proxy proxy-target-class="false">
来配置proxyMode
属性,默认基于类创建代理,属性值设置为false则基于接口创建代理
运行时值注入
Spring提供了两种在运行时求值的方式:
- 属性占位符(Property placeholder)
- Spring表达式语言(SpEL)
注入外部的值
- 处理外部值最简单的方式就是声明属性源并通过Spring的Environment来检索属性
示例:
@Configuration @PropertySource("classpath:/com/Eric/example.properties") // 声明属性源 public class ExpressiveConfig { @Autowired Environment env; @Bean public BlankDisc disc() { return env.getProperty("title"); } }
- 通过
@PropertySource
引用外部的属性源,并且该属性文件会加载到Spring的Environment中,可以在Environment中检索到属性
- 通过
深入学习Spring的Environment
Environment有四种
getProperty()
重载方法:String getProperty(String key)
: 通过key查找值,返回StringString getProperty(String key, String defaultValue)
: 通过key查找值,返回String,查找不到时使用默认值defaultValueT getProperty(String key, Class<T>type)
: 通过key查找值,并且将结果转换为T类型,查找不到时返回nullT getProperty(String key, Class<T>type, T defaultValue)
: 通过key查找值,并且将结果转换为T类型,查找不到时使用默认值defaultValue
containProperty()
: 检查某个属性是否存在getPropertyAsClass()
: 将属性解析为类String[] getActiveProfiles()
: 返回激活profile名称的数组String[] getDefaultProgiles()
: 返回默认profile名称的数组boolean acceptsProfiles(String...properties)
: 如果environment支持给定的profile,则返回true
解析属性占位符
- Spring支持将属性定义到外部的属性文件中,并使用占位符值将其插入到Spring bean中
- 占位符的形式为使用
${...}
包装的属性名称 在XML中注入参数示例:
<bean id="example" class="com.eric.Example" c:_title="${disc.title}" c:_artist="${disc.artist}"/>
在自动装配中通过
@Value
注解来实现public class BlankDisc { public BlankDisc( @Value("${disc.title}") String title, @Value("${disc.artist}") String artist) { this.title = title; this.artist = artist; } }
在使用占位符时,需要配置一个
PropertyPlaceholderConfigurer
bean或PropertySourcePlaceholderConfigurer
bean,推荐使用后者@Bean public static PropertySourcePlaceholderConfigurer placeholderConfigurer() { return new PropertySourcePlaceholderConfigurer(); }
<!-- 或者通过配置XML文件 --> <context:property-placdholder/>
使用Spring表达式语言进行装配
Spring Expression Language - SpEL,能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中
SpEL特性:
- 使用bena的ID来引用bean
- 调用方法和访问对象的属性
- 对值进行算术、关系和逻辑运算
- 正则表达式匹配
- 集合操作
SpEL样例:
- SpEL表达式需要放到
#{...}
中,与属性占位符类似 #{T(System).currentTimeMillis()}
T()
会将括号中的内容视为对应的Java对象
#{numb.artist}
- 通过bean的id(numb),来引用bean的属性(artist)
- SpEL表达式需要放到
在bean装配时运用表达式:
public class BlankDisc {
public BlankDisc(
@Value("#{systemProperties['disc.title']}") String title,
@Value("#{systemProperties['disc.artist']}") String artist) {
this.title = title;
this.artist = artist;
}
}
引用bean、属性和方法
- SpEL能够通过ID来引用对应的bean
#{bean.attribute}
引用id为bean
的attribute
属性#{bean.method()}
引用id为bean
的method
方法,获取方法返回值#{bean.method()?.toUpperCase()}
引用id为bean
的method
方法,获取方法返回值(假设此处的返回值为String),调用String的toUpperCase()
方法。?
用于判断方法返回值是否为null
(为null,则不调用后面的方法)
在表达式中使用类型
- 通过
T()
获取java中的类,例如#{T(java.lang.Math)}
获取Math类 - 此方法的价值在于能够访问目标类型的静态方法和常量,例如
#{T(java.lang.Math).PI}
获取PI的值
- 通过
SpEL运算符
- 示例:
- 计算圆的周长
#{2 * T(java.lang.Math).PI * circle.radius}
- 计算圆的面积
#{T(java.lang.Math).PI * circle.radius ^ 2}
- 合并String
#{disc.title + 'by' + disc.artist}
- 判断值
#{counter.total == 100}
或#{counter.total eq 100}
- 三元运算符
#{scoreboard.socre > 100 ? "Winner" : "Loser"}
- Elvis运算符
#{disc.title ?: 'Hello'}
判断值是否为null,为空则结果为'Hello'
- 计算圆的周长
- 示例:
计算正则表达式
- 通过
matches
来匹配 - 示例:
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-z-A-Z0-9.-]+\\.com'}
- 通过
计算集合
#{bean.list[i].attribute}
获取bean
中list
列表的i
个元素的attribute
属性#{'string'[i]}
获取字符串'string'
的第i
个字符- 查询运算符
.?[]
: 查询集合#{bean.list.?[attribute eq 'example']}
获取bean
中list
列表中所有attribute
属性为'example'
的元素.^[]
: 查询第一个匹配项.$[]
: 查询最后一个匹配项.![]
投影运算符