3.1 环境与profile
开发到生产阶段会有不同的环境,Spring所提供的解决方案就可以让代码在不同环境下不用重新构建
3.1.1 配置profile bean
Spring为环境相关的bean,根据环境决定创建或者不创建哪个bean,Spring不是在构建时做出决定,而是运行时,所以没有必要重新构建
在java配置类上,加上@Profile(“xxx”)
,这告诉了Spring这个配置类中的bean只有在xxx profile激活时才会创建
当然 @Profile也可以用在bean的方法上,即当规定的profile被激活时,相应的bean才会被创建,但是其他并不在任何profile作用域下的bean,无论何时都会被创建
我们也可以通过在XML文件中 < beans profile=“xxx”>元素(根元素)的profile属性来配置profile bean
同样可以在根元素< beans>下嵌套定义< beans>元素来提供多个profile作用域,而不是定义多个profile XML文件
3.1.2 激活profile
Spring通过两个属性来判断激活哪个profile
- spring.profiles.active:启用声明的profile
- spring.profiles.default:若没有使用spring.profiles.active,则会启用default的profile
- 如果两个都没有,那就只用创建哪些没有在profile中的bean
有多重方式可以来设置这两个属性:
- 作为DispatcherServlet的初始化参数
- 作为Web应用的上下文参数
- 作为JNDI条目
- 作为环境变量
- 作为JVM的系统属性
- 在测试类中使用@ActiveProfiles(“xxx”)
在web应用的web.xml文件中设置默认的profile:
<context-param>//为上下文指定默认的profile
<param-name>spring.profiles.default</param-name>
<param-value>dev<param-value>
</context-param>
<servlet>//为servlet指定默认的profile
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev<param-value>
</init-param>
</servlet>
3.2 条件化的bean
当我们想要一个bean,这个bean只有当某种条件存在时才会被创建,那就可以使用@Conditional(xxx.class),这个xxx必须实现Condition接口:
public interface Condition{
boolean matches(ConditionContext ctxt,AnnotatedTypeMetadata metadata);
}
只需实现matches方法,返回true,则创建哪些带有@Conditional的bean,反之不创建
ConditionContext是一个接口,大致如下:
public interface ConditionContext{
BeanDefinitionRegistry getRegistry();//检查bean定义
ConfigurationListableBeanFactory getBeanFactory();//检查bean是否存在,探查bean属性
Environment getEnvironment();//检查环境变量是否存在和它的值
ResourcesLoader getResourceLoader();//检查它加载的资源
ClassLoader getClassLoader();//加载并检查类是否存在
}
AnnotatedTypeMetadata也是一个接口,它能检查被Conditional修饰的bean上还有什么其他的注解
@profile注解本身实现也是用了@Condition注解,并且引用了ProfileCondition作为Condition实现,ProfileCondition实现了Condition接口,简化版如下:
@Conditionl(ProfileCOndition.class)
public @interface Profile{
String[] value();
}
3.3 处理自动装配的歧义性
即当标识@Autowired是一个接口,或者超类时,当该接口,或者超类有多个实现类,并都注册为bean,那么Spring就不知道该自动装配哪一个,这就产生了歧义
3.3.1 标识首选的bean
在子类bean上添加@Primary来标识最喜欢的方案
或者在XML中使用<bean ...... primary="true" />
3.3.2 限定自动装配的bean
使用限定符(默认为bean id),在超类@Autowired的地方使用@Qualifier(“xxx”)可以将可选bean限定到xxx
也可以自定义限定符,在想自定义限定符的bean上加上@Qualifier(“name”)
于是name就成为了该类的限定符
3.4 bean的作用域
默认情况下Spring应用上下文中所有bean都是单例的
但如果所使用的的类是易变的,会保持一些状态,,那么单例重用就不安全
于是Spring提供了多种作用域:
- 单例(Singleton):在整个应用只会创建一个bean实例
- 原型(Prototype):每次出入货通过Spring应用上下文获取时,都会创建新的bean实例
- 会话(Session):在Web应用中,为每一个会话创建一个新的bean实例
- 请求(Request):在Web应用中,为每一个请求创建一个新的bean实例
如果想使用其他作用域,就可以使用@Scope注解,如:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
3.4.1 使用会话和请求作用域
有些时候我们需要使用会话作用域,如购物车ShoppingCart类,可以将设置为:
@Scope(value=WebApplicationContext.SCOPE_SESSION),ProxyMode=ScopedProxyMode.INTERFACES)
但是当需要把ShoppingCart类注入到单例类(如StoreService)时,就会出问题,这就使用了ProxyMode属性,该属性指定了一个代理,这个代理要实现ShoppingCart接口,然后就可以通过代理和单例类打交道
3.4.2 在XML中声明作用域代理
如:
<bean id="xxx"
class="xxx"
scope="session">
<aop:scoped-proxy />//要先声明aop的命名空间
</bean>
3.5 运行时值注入
- 属性值注入
- Spring表达式语言(SpEL)
3.5.1 注入外部的值
1.使用@PropertySource注解和Environment,如:
@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")//声明属性源
puvlic class xxx{
@Autowired
Environment env;
...
...
public BlankDisc disc(){
return new BlankDisc(env.getProperty("disc.title"),
env.getProperty("disc.artist"));//检索属性值
}
}
app.properties:
disc.title=xxx
disc.artist=xxx
如果不仅想装配String属性就可以使用重载的getProperty()
- T getProperty(String key,Class type,T defaultValue)
解析属性占位符
Spring一直支持将属性定义放到外部的属性文件中,并使用占位符将其插入到Spring bean中,如:${...}
包装属性名称
XML中可以直接使用如:c:_title="${disc.title}"
自动装配或者JavaConfig使用@Value来使用:
public BlankDisc(@Value("$disc.title") Stirng title){
this.title=title;
}
但是要使用占位符必须先配置一个PropertySourcesPlaceholderConfigurer类,或者在XML文件中加上<context:property-placeholder />
3.5.2 使用Spring表达式语言进行装配
使用SpEL表达式会在运行时计算得到值,SpEL表达式要放到:
#{...}
中,这里面的就是SpEL的表达式体,如:
#{T(System).CurrentTimeMillis()}//获取当前时间
#{sgtPeppers.artist}//引用其他bean的属性或者方法返回值
#{systemProperties['disc.title']}//通过systemProperties引用配置文件属性
#{admin.email matches '.....'}//匹配正则表达式
#{xxx.nums[4].name}//引用集合中的数