Spring in Action --- 第三章 高级装配

Spring profile

使用Spring profile 能够设定在不一样的环境中启用不一样的bean.因为环境的不一样,数据库配置,加密算法以及与外部系统的集成可能会有不一样的表现,特别是数据库,通常开发环境,QA环境,预发布环境,生产环境会分开java

Spring并非在构建的时候作出这样的决策的,而是等到运行时再来肯定web

在java中

使用@Porfile注解指定某个bean属于哪个profile,算法

@Profile("dev")

表示profile环境是dev.spring

只有相应的profile激活的时候,才会建立对应的bean,可是,没有指定profile的bean始终都会被建立,与激活哪一个profile没有关系数据库

在XML中

<beans xmlns="http://springframework.org/schema/beans"
        xmlns:...
 profile="dev">
 </beans>

建立不一样的XML文件设置不一样的profile,也能够在根<bean>元素中嵌套定义<beans>元素,而不是为每个环境建立一个profile XML文件.app

激活 profile

Spring在肯定哪一个profile处于激活状态时,须要依赖两个独立的属性:spring.profiles.activespring.profiles.default,若是设置了 spring.profiles.active 属性,那么它的值就会用来肯定哪一个profile是激活的,可是若是没有设置,那 Spring会查找spring.profiles.defalut的值.若是二者均为设置,那么就没有激活的profile,Spring只会建立哪些没有定义在profile中的bean.oop

设置属性的方式
  • 做为 DispatcherServlet 的初始化参数
  • 做为 Web 应用的上下文参数
  • 做为 JNDI 条目
  • 做为环境变量
  • 做为JVM的系统属性

示例:
在web.xml中为上下文设置默认的profile测试

<context-param>
    <param-name>spring.profiles.default</param-name>
    <param-value>dev</param-value>
</context-param>

在web.xml中为Servlet设置默认的profileui

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>
        prg.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </init-param>
</servlet>

使用profile进行测试

Spring提供了@ActiveProfiles注解指定运行测试时要激活哪一个profile.this

条件化的 bean

Spring4之后,引入了@Conditional,能够用到带有@Bean注解的方法上,用于计算给定的条件若是为true,就会建立这个bean,不然的话这个bean会被忽略
例若有一个Test的类,咱们但愿只有设置了 test 环境属性的时候,Spring才会实例化这个类,若是没有,就会被忽略.下面的配置展现了注解的使用方式

@Bean
@Conditional(TestExistsCondition.class)
public Test test() {
    return new Test();
}

@Condition中给定了一个Class,指明建立该bean的条件,设置给它的类能够是任意实现了Condition接口的类型.下面展示了TestExistsCondition类的实现

public class TestExistsCondition() {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment env = context.getEnvironment();
    return env.containsProperty("test");
    }
}

其中的两个参数 ConditionContext, AnnotatedTypeMetadata 都是接口,具体请看P77

处理自动装配的歧义性

在使用自动装配时,若是一个接口有多个实现类而且都被定义为bean,当Spring试图装配的时候就会没法作出选择从而抛出 NoUniqueBeanDefinitionException, Spring提供了两种方式来解决这样的问题:

  • 将可选bean的某一个设置为首选的bean
  • 使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小到只有一个bean

标示首选的bean

在 bean 上使用@Primary注解标示当前的bean是首选bean,若是你是用XML来实例化bean
,能够用下面的方式:

<bean id="iceCream" class="com.IceCream" primary="true">
</bean>

可是,若是你设置了两个或者更多的首选bean,那么它就没法正常工做了

限定自动装配的bean

@Qualifier注解是使用限定符的主要方式:

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
    this.dessert = dessert
}

上面的代码片断是最简单的例子,为@Qualifier注解所设置的参数就是想要注入的bean的ID,全部的@Component注解建立的类的ID都是首字母变为小写的类名.须要注意的是,若是没有指定其余的限定符的话,全部的bean都会给定一个默认的限定符,这个限定符与bean的ID相同

这里有个问题就是,指定的限定符与要注入的bean的名称是紧耦合的,若是bean的名字修改了,那么就会没法注入,解决方案是在bean上使用@qualifier指定该bean的限定符

使用自定义的限定符注解

若是iceCream同时被使用在两个或更多的bean上,那怎么办呢?你可能会想到的是使用更多的限定符来缩小范围:

@Component
@Qualifier("iceCream")
@Qualifier("cold")
public class IceCream implements Dessert {...}

这里只有一个小问题,Java不容许在同一个条目上重复出现相同类型的多个注解.若是你这么作了,编译将会报错.可是,咱们能够建立自定义的限定符注解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Qualifier
public @interface Cold{}

上面的代码片断定义了一个自定义注解,它自己要使用@Qualifier来标注,这样就能够不断缩小范围,直到惟一.

bean 的做用域

在默认状况下,Spring应用上下文中全部的bean都是做为以单利的形式建立的,不过,Spring提供了多种做用域,能够基于这些做用域建立bean:

  • 单例:在整个应用中,只建立bean的一个实例
  • 原型:每次注入或者经过Spring应用上下文获取的时候,都会建立一个新的bean实例
  • 会话:在Web应用中,为每一个会话建立一个bean实例
  • 请求:在Web应用中,为每一个请求建立一个bean实例

默认是单例模式,若是选择其余的做用域,要使用@Scope注解,例如,要将一个bean生命为原型,须要这样作:

@bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad{...}

使用组件扫描来发现和声明bean,只须要加上@Component注解替换@Bean,一样,若是使用XML来配置bean的话,可使用scope属性来设置做用域:

<bean id="notepad" class ="com.notepad" scope="prototype" />

对于会话做用域,在当前会话的相关操做中,bean其实是单例的:

@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() {...}

要注意的是,同时还有一个proxyMode属性被设置,请看下面的代码:

@Component
public class StoreService {
    @Autowired
    public void setShoppingCart(ShoppingCart shoopingCart) {
        this.shoppingCart = shoppingCart
    }
}

这个service是一个单例的bean,当它建立的时候,Spring会试图将ShoppingCart bean注入,可是ShoppingCart是会话做用域的,此时并不存在,知道某个用户进入到系统才会出现实例,另外,系统中将会有多个ShoppingCart实例被建立,咱们但愿注入的bean恰好是当前会话对应的那一个,所以,Spring会向StroeService中注入一个到ShoppingCart的代理.关于具体的描述,请看P87

运行时注入

在代码中,最好的方式是不要在代码中出现硬代码,所以咱们须要借助Spring的一些配置来注入外部的值:

@Configuration
@PropertySource("")
public class ExpressiveConfig {
    @Autowired
    Environment env;
    
    @Bean
    public BlankDisc disc() {
        return new BlankDisc(env.getProperty("disc.title"));
    }
}

咱们使用了@ PropertySource注解引用了外部的一个属性文件,其中有disc.title属性,会被读取并配置到bean中,若是这个属性没有被定义,获取到的值会是null,若是你但愿这个属性必须被定义,可使用getRequiredProperty()方法.

另外,可使用Environment.containsProperty()方法来判断是否有某一个属性.最后,若是想将属性解析为类的话,可使用getPropertyAdClass()方法.

若是咱们依赖组件扫描和自动装配来建立和初始化应用组件的话,娜美可使用@Value注解,它与@Autowired很是类似:

public BlankDisc(@Value("${disc.title}") String title) {
    this.title = title;
}

为了使用占位符,咱们必需要配置一个PropertyPlaceHolderConfigurer bean或 PropertySourcePlaceHolderConfigurer bean,具体请看P92

注解

@Profile

Spring 3.2之后,注解支持在类和方法级别上使用
@Profile("...")括号中能够定义任意的字符串,但建议用有意义的字符串

@ActiveProfiles

@Conditional

@Primary

@Qualifier

@Scope

@PropertySource

相关文章
相关标签/搜索