目录
3.1 环境与profile
数据库配置、加密算法以及与外部系统的集成是跨环境部署时会发生变化的几个典型例子
Spring引入了bean profile的功能。要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。
- 在Java 中配置profile bean
在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile。示例如下:
@Configuration
@Profile("dev")
Public class DevelopmentProfileConfig {
}
从Spring3.2开始,你也可以在方法级别上使用@Profile注解,与@Bean注解一同使用,这样的话,就能将这两个bean的声明放到同一个配置类
- 在 XML 中配置profile bean
也可以通过<beans>元素的profile属性,在XML中配置profile bean。
使用 XML 配置profile bean示例:
- 激活profile
Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。如果spring.profiles.active和spring.profiles.default均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean
有多种方式来设置这两个属性:
- 作为DispatcherServlet的初始化参数;
- 作为Web应用的上下文参数;
- 作为JNDI条目;
- 作为环境变量;
- 作为JVM的系统属性;
- 在集成测试类上,使用@ActiveProfiles注解设置。
示例:
3.2 条件化的 bean
是Spring4引入了一个新的@Conditional注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean会被忽略。
设置给@Conditional的类可以是任意实现了Condition接口的类型。可以看出来,这个接口实现起来很简单直接,只需提供matches()方法的实现即可。如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。如果matches()方法返回false,将不会创建这些bean。
matches()方法包含ConditionContext参数,通过ConditionContext,我们可以做到如下几点:
- 借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
- 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
- 借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
- 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
- 借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
3.3 处理自动装配的歧义性
仅有一个bean匹配所需的结果时,自动装配才是有效的。如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍Spring自动装配属性、构造器参数或方法参数
当确实发生歧义性的时候,Spring提供了多种可选方案来解决这样的问题。你可以将可选bean中的某一个设为首选(primary)的bean,或者使用限定符(qualifier)来帮助Spring将可选的bean的范围
缩小到只有一个bean。
- 标示首选的bean
在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装配时的歧义性。
示例:
@Component
@Primary
Public class Ice CreamimplementsDessert
{...}
如果你使用XML配置bean的话,同样可以实现这样的功能。<bean>元素有一个primary属性用来指定首选的bean
- 限定自动装配的 bean
Spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限制条件。
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。
Java不允许在同一个条目上重复出现相同类型的多个注解。
当你不想用@Qualifier注解的时候,可以类似地创建@Soft、@Crispy和@Fruity。通过在定义时添加@Qualifier注解,它们就具有了@Qualifier注解的特性。它们本身实际上就成为了限定符注解。
3.4 bean 的作用域
在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。
Spring定义了多种作用域,可以基于这些作用域创建bean,包括:
- 单例(Singleton):在整个应用中,只创建bean的一个实例。
- 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
- 会话(Session):在Web应用中,为每个会话创建一个bean实例。
- 请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
单例是默认的作用域,但是正如之前所述,对于易变的类型,这并不合适。如果选择其他的作用域,要使用@Scope注解,它可以与@Component或@Bean一起使用
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Public Notepad notepad()
{
return new Notepad();
}
就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用@Scope注解,它的使用方式与指定原型作用域是相同的
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
Public ShoppingCart cart()
{...}
Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理,如图3.1所示。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean
3.5 运行时植入
有时候硬编码是可以的,但有的时候,我们可能会希望避免硬编码值,而是想让这些值在运行时再确定。为了实现这些功能,Spring提供了两种在运行时求值的方式:
- 属性占位符(Propertyplaceholder)。
- Spring表达式语言(SpEL)。
Spring一直支持将属性定义到外部的属性的文件中,并使用占位符值将其插入到Springbean中。在Spring装配中,占位符的形式为使用“${...}”包装的属性名称。
如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了。在这种情况下,我们可以使用@Value注解
public BlankDisc(@Value("${disc.title}") String title,@Value("${disc.artist}") String artist)
{
this.title=title;
this.artist=artist;
}
为了使用占位符,我们必须要配置一个PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。从Spring3.1开始,推荐使用PropertySourcesPlaceholderConfigurer,因为它能够基于SpringEnvironment及其属性源来解析占位符
SpEL表达式要放到“#{...}”之中,这与属性占位符有些类似,属性占位符需要放到“${...}”之中。
SpEL表达式也可以引用其他的bean或其他bean的属性。除了引用bean的属性,我们还可以调用bean上的方法。
publicBlankDisc(@Value("#{systemProperties['disc.title']}") String title,@Value("#{systemProperties['disc.artist']}") String artist)
{
this.title=title;
this.artist=artist;
}
如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关键的运算符