3.Spring学习笔记之高级装配

1.环境与profile

Spring为环境相关的bean所提供的解决方案其实与 构建时的方案没有太大的差别。当然,在这个过程中需要根据环境决定创建哪个bean和不创建哪个bean。不过Spring并不是在构建的时候做出这样的决策,而是等到运行的时再来确定。这样的结果就是同一个部署单元(可能会使war文件)能够适用于所有的环境,没有必要重新 构建。

在3.1版本中,Spring引入了bean profile的功能。要使用profile,首先需要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。

在Java配置中,使用@Profile注解指定某个bean属于哪一个profile

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig{
    @Bean(destroyMethod="shutdown")
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder();
    }
}

@Profile应用在了类级别上,它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。
从Spring 3.2开始,可以在方法级别上使用@Profile注解,与@Bean注解一起使用。

@Configuration
public class DevelopmentProfileConfig{
    @Bean(destroyMethod="shutdown")
    @Profile("dev")
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder();
    }
}

而没有指定profile的bean始终都会被创建,与激活哪一个profile没有关系。

在XML中配置profile
我们也可以通过<beans>元素的profile属性,在XML中配置profile bean。

<beans 
    ...
    profile="dev">
    <jdbc:embedded-database id="dataSource">
        ...
    </jdbc:embedded-database>
</beans>

你可以在根<beans>元素中嵌套定义<beans>元素,而不是为每个环境都创建一个profile XML文件。这能将所有的profile bean定义放到同一个XML文件中。

激活profile

Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.activespring.profiles.default
如果设置了active属性的话,那么它的值就会用来确定哪个profile是激活的。但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。如果这两个属性都没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean。

有多种方式来设置这两个属性:

  • 作为DispatcherServlet的初始化参数
  • 作为Web应用的上下文参数
  • 作为JDNI条目
  • 作为环境变量
  • 作为JVM的系统属性
  • 在集成测试类上,使用@ActiveProfiles注解设置

例如,在Web应用中,设置spring.profiles.default的Web.xml文件如下所示:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.4"
        xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>        

    <!-- 为上下文设置默认的profile -->
    <context-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </context-param>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 为Servlet设置默认的profile -->
        <init-param>
            <param-name>spring.profiles.default</param-name>
            <param-value>dev</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

按照这种方式设置spring.profiles.default,所有的开发人员都能从版本控制软件中获得应用程序源码,并使用开发环境的设置(如嵌入式数据库)运行代码,而不需要任何额外的配置。
当应用程序部署到QA、生产或其他环境之中时,负责部署的人根据情况使用系统属性、环境变量或JNDI设置spring.profiles.active即可。当设置active以后,default设置成什么值已经无所谓了,系统会优先使用spring.profiles.active中所设置的profile而且,你可以同时激活多个profile,通过列出多个profile名称,并以逗号分隔来实现。

Spring提供了@ActiveProfiles注解,来指定测试时要激活哪个profile。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
...
}

在条件化创建bean方面,Spring的profile机制是一种很棒的方法,这里的条件要基于哪个profile处于激活状态来判断。Spring 4.0提供了一个更为通用的机制来实现条件化的bean定义。

2.条件化的bean——使用Spring 4和@Conditional注解定义条件化的bean

Spring4 引入了一个新的@Conditional注解,可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话这个bean会被忽略。

@Bean
@Conditional(MagicExistsCondition.class)
public  MagicBean magicBean() {
    return new MagicBean();
}

设置给@Conditional的类可以是任意实现了Condition接口的类型。(这个接口实现起来很简单,只需要提供matches()方法的实现即可,如果返回matches()方法返回true,那么就会创建带有@Conditional注解的bean。如果matches()方法返回false,将不会创建这些bean。)

matches()方法会得到ConditionContext和AnnotatesTypeMetadata对象用来做出决策。
通过ConditionContext,我们可以做到如下几点:

  • 借助getRegistry()返回的BeanDefinitionRegistry检查bean定义
  • 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性
  • 借助getEnviroment()返回的Environment检查环境变量是否存在以及它的值是什么
  • 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源
  • 借助getClassLoader()返回的ClassLoader加载并检查类是否存在

AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有其他的注解。借助isAnnotated()方法,我们能够判断带有@Bean注解的方法是不是还有其他特定的注解。借助其他的那些方法,我们能够检查@Bean注解的方法上其他注解的属性。

从Spring 4 开始,@Profile注解进行了重构,使其基于@Conditional和Condition实现。

3.处理自动装配的歧义性

当自动注入时遇到有多个可选方案时,系统会抛出NoUniqueBeanDefinitionException异常。
Spring提供了两种方法来解决歧义性:

  1. 标示首选的bean:当创建bean的时候,将其中一个可选的bean设置为首选(primary)bean,当遇到歧义性的时候,Spring将会使用首选的bean。@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中。
@Component
@Primary
public class IceCream implements Dessert {...}
@Bean
@Primary
public Dessert iceCream() {
    return new IceCream();
}

XML:

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

2.限定自动装配的bean
@Qualifier注解是使用限定符的主要形式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入的是哪个bean。

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

@Qualifier(“iceCream”)所引用的bean要具有String类型的“iceCream”作为限定符。如果没有指定其他的限定符的话,所有的bean都会给定一个默认的限定符,这个限定符与bean的ID相同。

我们可以使用@Qualifier和@Component组合使用来自定义类限定符,而不是依赖bean ID作为限定符。

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

@Qualifier也可以与@Bean一起使用

创建自定义注解

我们也可以创建自定义的限定符注解,借助这样的注解来表达bean所希望限定的特性。所需要做的就是创建一个注解,其本身要使用@Qualifier注解来标注。例如,创建一个@Cold注解

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

使用:

@Component
@COld
@Creamy
public class IceCream implements Dessert {...}
@Autowried
@Cold
@Creamy
public void setDessert(Dessert dessert) {
    this.dessert = dessert;
}

在这里@Cold和@Creamy都是自定义的限定符注解,它们可以组合使用。

4.bean的作用域

在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式来创建的。
Spring定义了多种作用域,可以基于这些作用域创建bean,包括:

  • 单例(Singleton):在整个应用中,只创建bean的一个实例。
  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean的实例。
  • 会话(Session):在Web应用中,为每个会话创建一个bean实例。
  • 请求(Request):在Web应用中,为每次请求创建一个bean实例。

单例是默认的作用域,如果选择其他的作用域,可以使用@Scope注解,例如声明为原型:

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

这里使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了原型作用域,也可以直接使用@Scope(“prototype”),只不过上面更加安全并且不易出错。
使用xml的话,可以使用<bean>元素的scope属性来设置作用域:

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

使用会话和请求作用域

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

将其注入到单例StoreService bean的Setter方法中:

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

因为StoreService是一个单例bean而ShoppingCart是一个会话作用域bean,所以,当StoreService实例创建的时候,ShoppingCart并没有创建,而且由于这两个bean之间是一对多的关系,我们并不想让一个固定的ShoppingCart bean实例注入到StoreService中。
实际上,Spring也并不会将ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理,这个代理会暴露于ShoppingCart相同的方法,所以StoreService会认为它就是一个ShoppingCart 实例(其实并不然)。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给当前会话作用域内真正的ShoppingCart bean。
如之前的代码所示,proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这表明这里代理实现ShoppingCart接口,并将调用委托给时间bean。如果ShoppingCart不是接口而是类的话,需要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。

使用xml的话:

<bean id="cart" class="com.myapp.ShoppingCart" scope="session">
<aop:scope-proxy proxy-target-class="false" />
</bean>

默认情况下,它会使用CGLib创建目标代理,也就是类代理。通过将proxy-target-class属性设置为false,进而要求它生成基于接口的代理。

5.运行时值注入

Spring提供了两种在运行时求值的方式:

  • 属性占位符
  • Spring表达式语言

在Spring中处理外部值的最简单方式就是声明属性源并通过Spring的Enviroment来检索属性。

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties") //声明属性源
public class EcpressiveConfig {
    @Autowried
    Environment env;
    @Bean
    public BlankDisc disc() {
        return new BlankDisc(
            env.getProperty("disc.title");//检索属性值
        )
    }
}

深入学习Spring的Environment

Environment的getProperty方法有四个重载的变种形式:

  1. String getProperty(String key)
  2. String getProperty(String key, String defaultValue)
  3. T getProperty(String key, Class<T> type )
  4. T getProperty(String key, Class<T> type, T defualtValue)

如果希望一个属性必须要定义,可以使用getRequiredProperty()方法,如果这个属性没有定义的话,将会抛出IllegalStateException异常。
检查某个属性是否存在:env.containsProperty(“disc.artis”);
将属性解析为类:getPropertyAsClass();
Environment还提供了一些方法来检查哪些profile处于激活状态

  • String[] getActiveProfiles();返回激活profile名称的数组;
  • String[] getDefaultProfiles();返回默认profile名称的数组;
  • boolean acceptsProfiles(String…profiles):如果Environment支持给定profile的话,就返回true

解析属性占位符

在Spring装配中,占位符的形式为“${…}”包装的属性名称。

<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="${disc.title}" />

如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话,那么就米有指定占位符的配置文件或类了。在这种情况下,我们可以使用@Value注解,它的使用方法与@Autowired注解非常类似:

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

为了使用占位符,我们需要配置一个PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean.从Spring 3.1开始,推荐使用后一个,因为它能够基于Spring Environment及其属性源来解析站位符。

@Bean
public static PropertySourcesPlaceholderConfigurer placehoiderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}

如果使用xml配置的话,Spring context命名空间的<context:property-placeholder>元素将会为你生成PropertySourcesPlaceholderConfigurer bean:

<beans
...
记得要添加命名空间,太多此处已省略,哈哈>
<context:property-placeholder />
</beans>

使用Spring表达式语言进行装配(SpEL)

Spring3 引入了Spring表达式语言,它能够以一种强大和简介的方式将值装配到bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。例如:

public BlankDisc(@Value("#{systemProperties['disc.title']}" String title)){
this.title = title;
}

这里#{systemProperties[‘disc.title’]}就是SpEL表达式。
至于SpEl,由于在这里不能面面俱到的介绍它,将在后面用专门的一篇来介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值