部署环境
在软件开发中,部署环境的改变对软件本身的影响是一件很棘手的事情。如开发环境和生成环境等等。
spring的策略是在运行时做出决定,而不是编译时。
使用@Profile注解去指明该bean属于哪一个环境。
@Profile("dev")
在spring3.1版本中,@Profile还只能用在class级别上,从3.2版本开始@Profile可以用在方法级别上了,控制粒度更细了。
在xml配置文件中使用profile属性配置,
<beans profile="dev">
是beans级别的属性
问题:如何使某个profile处于active状态
spring提供了两个参数控制Profile的状态:
spring.profiles.active 和 spring.profiles.default
spring.profiles.active 这个值设置了谁,谁就是active的。
如果spring.profiles.active没有设置,那么spring.profiles.default设置了谁,谁就是active的。
如果两个都没有设置,那么全都不是active的。所有被@Profile注解的都会无效。
问题:这两个参数的设置方法
- DispatcherServlet的初始化参数
- web application的context 参数
- jndi
- 环境变量
- jvm环境变量
- 在单元测试方法中使用@ActiveProfiles注解
如,在web.xml中配置:
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
或者servlet中配置初始化参数
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
如单元测试中:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
... }
Spring4提供的更强大的@Conditional注解
应用在bean级别的注解,粒度更加细化。只有@Conditional注解的条件判断为true时,bean才会被创建。
如:
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {
return new MagicBean();
}
如何使用呢?@Conditional和Condition接口是成对出现的。
public interface Condition {
boolean matches(ConditionContext ctxt,AnnotatedTypeMetadata metadata);
}
我们要做的就是实现matches方法,它判断为true就创建,判断为false就不创建bean。
public class MagicExistsCondition implements Condition {
public boolean matches(
ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("magic");
}
}
注意到matches方法有两个参数,其中ConditionContext是一个接口
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
ClassLoader getClassLoader();
}
从方法名来看,我们应该可以得到这些信息:
- 得到bean注册信息,getRegistry
- 得到bean工厂
- 得到环境变量
- 得到资源加载器
- 得到类加载器
AnnotatedTypeMetadata也是一个接口,是获取注解信息的。
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationType);
Map<String, Object> getAnnotationAttributes(String annotationType);
Map<String, Object> getAnnotationAttributes(
String annotationType, boolean classValuesAsString);
MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType);
MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType, boolean classValuesAsString);
}
解决自动注入的ambiguity问题
前面讲到,在自动注入的时候,当发现有多于1个的合适的bean可以注入时,spring是无法做决定的,报ambiguity错误。
1. 使用@Primary注解
在类上使用@Component注解为bean时,同时使用@Primary注解,表明这是最优秀的bean。
在bean上使用@Bean注解时,加上@Primary。多个时选择最优先的bean注入。
xml中配置:bean标签中设置属性primary=”true”
2. 使用限定注解@Qualifier
可以和@Autowired连用
如:
@Autowired
@Qualifier("iceCream")
意思是注入beanId是iceCream的bean,准确的说是beanId中有这个字符串的bean。
spring在创建bean是,默认使用它的小写类名作为beanId。
这样会导致的一个问题是,当改变类名时,beanId会改变,注入的地方的@Qualifier如果忘记改了,就会出错。
有个办法是,在声明bean时,也加上@Qualifier注解,此时的作用是定义自己的限定条件。如:
@Component
@Qualifier("cold")
这样定义了自己的限定,使用时就可以
@Autowired
@Qualifier("cold")
@Qualifier和@Bean这种确定性注解放在一起是不起作用的。
@Qualifier可以同时使用多个。
但是,注意,java是不允许同时使用多个相同的注解的。java8允许,但该注解必须被@Repeatable注解,but我们的@Qualifier并没有。
所有有个变通的方式是,自定义注解。使用@Qualifier注解。如:
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold { }
可以再定义一个
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Happy { }
这样就可以同时用多个了。
Scope
- Singleton 。整个application范围单例。默认。
- Prototype。每次注入都创建新的。
- Session。在web应用中,每个session创建一个新的。
- Request。在web应用中,每个request创建一个新的。
配置scope:
在javaConfig中使用@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
注解
在xml中为<bean>
使用scope="prototype"
session的scope:
@Scope(
value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
注意proxyMode是用来解决将session或者request级别的bean注入singleton的bean时会遇到的问题。因为一个singleton的bean是在spring的application context 加载的时候创建的,而session或者request级别的bean是在session或者request创建时才创建出来的,所以此时还不存在。spring的解决办法是为这个session或者request级别的bean创建一个代理,将其注入singleton的bean。
注意上面的例子中我们使用的是接口ScopedProxyMode.INTERFACES。
也就是说此时注解是使用在一个接口上面。
如果是class类,要用ScopedProxyMode.TARGET_CLASS。spring会使用CGLib去创建基于类的代理。
在xml中配置代理:
使用
<aop:scoped-proxy />
默认情况下是使用CGLIB生成基于类的代理。如果要使用接口,用
<aop:scoped-proxy proxy-target-class="false" />
spring的EL表达式
spring的el表达式主要是为了运行时动态的注入数据,而不是写死。
在看spel之前,还有另一种方法也可以达到这个目的:属性占位符(property placeholders)。
例子:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig {
@Autowired
Environment env;
@Bean
Declare a property source
public BlankDisc disc() {
return new BlankDisc(
env.getProperty("disc.title"),
env.getProperty("disc.artist"));
} }
Environment还有很多有用的方法,具体看api。
在xml中使用,${xxx}占位
spel提供的增强能力:
- 通过id访问bean
- 在对象上执行方法或者获取属性
- 数学,关系,逻辑运算
- 正则表达
- 集合操作
使用 #{ … },里面的就是spel表达式
如#{T(System).currentTimeMillis()}
得到系统时间。T()操作符,返回的是class。所以只能去访问静态方法和常量。
#{sgtPeppers.artist}
得到id是sgtPeppers的artist属性值
#{systemProperties['disc.title']}
得到系统参数值
#{artistSelector.selectArtist()?.toUpperCase()}
这个例子selectArtist返回string,然后对其Upper,?是为了解决空指针问题。
操作符:
选择操作符
#{jukebox.songs.?[artist eq 'Aerosmith']}
jukebox.songs返回list,.?[]操作符根据后面的表达式进行选取,true的都会返回。
.^[]返回第一个匹配的。
.$[] 返回最后一个匹配的。
投射操作符.![]
#{jukebox.songs.![title]}
jukebox.songs.返回的是一个Song类型的list,对其使用.![]作用是取每一个的title属性值,组成新的String的list返回。