第3章 高级装配

来源:互联网 发布:php 字符串转换成html 编辑:程序博客网 时间:2024/05/19 17:22
高级装配:
  • Spring profile 
  • 条件化的bean声明 
  • 自动装配与歧义性 
  • bean的作用域 
  • Spring表达式语言

3.1 环境与profile
@bean(destroyMethod="shutdown")
public DataSource dataSource()
{
    return new EmbeddedDatabaseBuilder()
        .addScript("classpath:schema.sql")
        .addScript("classpath:test-data.sql")
        .build();
}
    这会创建一个类型为javax.sql.DataSource的bean,这个bean是 如何创建出来的才是最有意思的。使用EmbeddedDatabaseBuilder会搭建一个嵌入式的Hypersonic数 据库,它的模式(schema)定义在schema.sql中,测试数据则是通过 test-data.sql加载的。

schema.sql:    create table Things (id identity,name varchar(100));
test-data.sql:    insert into Things (name) values ('A')

    当你在开发环境中运行集成测试或者启动应用进行手动测试的时候, 这个DataSource是很有用的。每次启动它的时候,都能让数据库处 于一个给定的状态。

    尽管EmbeddedDatabaseBuilder创建的DataSource非常适于开 发环境,但是对于生产环境来说,这会是一个糟糕的选择。在生产环境的配置中,你可能会希望使用JNDI从容器中获取一个DataSource。
@Bean
public DataSource dataSource()
{
    JudiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
    jndiObjectFactoryBean.setJndiName("jdbc/myDS");
    jndiObjectFactoryBean.setResourceRef(true);
    jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
    return (DataSource) judiObjectFactoryBean.getObject();
}

    通过JNDI获取DataSource能够让容器决定该如何创建这 个DataSource,甚至包括切换为容器管理的连接池。即便如此, JNDI管理的DataSource更加适合于生产环境,对于简单的集成和开发测试环境来说,这会带来不必要的复杂性。
    在QA环境中,你可以选择完全不同的DataSource配置,可 以配置为Commons DBCP连接池。
@Bean(destoryMethod="close")
public DataSource dataSource()
{
    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
    dataSource.setDriverClassName("org.h2.Driver");
    dataSource.setUsername("sa");
    dataSource.setInitialSize(20);
    dataSource.setMaxActive(30);

    return dataSource;
}

    的三个版本的dataSource()方法互不相同。虽然 它们都会生成一个类型为javax.sql.DataSource的bean,但它们 的相似点也仅限于此了。每个方法都使用了完全不同的策略来生成DataSource bean。
    这里的讨论并不是如何配置DataSource(我们将会 在第10章更详细地讨论这个话题)。
    在不同的环境 中某个bean会有所不同。我们必须要有一种方法来配置DataSource,使其在每种环境下都会选择最为合适的配置。

    一种方式就是在单独的配置类(或XML文件)中配置每个bean, 然后在构建阶段(可能会使用Maven的profiles)确定要将哪一个配置 编译到可部署的应用中。这种方式的问题在于要为每种环境重新构建应用。当从开发阶段迁移到QA阶段时,重新构建也许算不上什么大 问题。但是,从QA阶段迁移到生产阶段时,重新构建可能会引入bug 并且会在QA团队的成员中带来不安的情绪。
    Spring所提供的解决方案并不需要重新构建。

3.1.1 配置profile bean
    Spring为环境相关的bean所提供的解决方案其实与构建时的方案没有 太大的差别。当然,在这个过程中需要根据环境决定该创建哪个bean 和不创建哪个bean。不过Spring并不是在构建的时候做出这样的决策,而是等到运行时再来确定。这样的结果就是同一个部署单元(可 能会是WAR文件)能够适用于所有的环境,没有必要进行重新构建。
    在3.1版本中,Spring引入了bean profile的功能。要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。
    在Java配置中,可以使用@Profile注解指定某个bean属于哪一个 profile。例如,在配置类中,嵌入式数据库的DataSource可能会配 置成如下所示:
package com.myapp;
import javax.activation.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.dataSource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig
{
    @Bean(destroyMethod="shutdown")
    public DataSource dataSource()
    {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .addScript("classpath:test-data.sql")
            .builid();
    }
}
    @Profile注解应用在了类级别上。它会告诉 Spring这个配置类中的bean只有在dev profile激活时才会创建。如果 dev profile没有激活的话,那么带有@Bean注解的方法都会被忽略掉。
    同时,你可能还需要有一个适用于生产环境的配置,如下所示:
package com.myapp;
import javax.activation.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jndi.JndiObjectFactoryBean;

@Configuration
@Profile("prod")
public class ProductionProfileConfig
{
    @Bean
    public DataSource dataSource()
    {
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDS");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndiObjectFactoryBean.getObject();
    }
}

你也可以在方法级别上使用@Profile注解, 与@Bean注解一同使用。这样的话,就能将这两个bean的声明放到同 一个配置类之中,如下所示:
package com.myapp;
import javax.activation.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jndi.JndiObjectFactoryBean;

@Configuration
public class DataSourceConfig
{
    @(destoryMethod="shutdown")
    @Profile("dev")
    public DataSource embeddedDataSource()
    {
        return new EmbeddedDatabaseSource()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .addScript("classpath:test-data.sql")
            .build();
    }
    @Bean
    @Profile("prod")
    public DataSource jndiDataSource()
    {
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDS");
        jndiObjectFactoryBean.setResource(true);
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndiObjectFactoryBean.getObject();
    }
}

在XML中配置profile
    我们也可以通过<beans>元素的profile属性,在XML中配置 profile bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans";
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
    xmlns:jdbc="http://www.springframework.org/schema/jdbc";
    xsi:schemaLocation="
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframawork.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd";
    profile="dev">

<jdbc:embedded-database id="dataSource">
    <jdbc:script location="classpath:schema.sql" />
    <jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans>
    与之类似,我们也可以将profile设置为prod,创建适用于生产环境的从JNDI获取的DataSource bean。同样,可以创建基于连接池定义的DataSource bean,将其放在另外一个XML文件中,并标注为qaprofile。所有的配置文件都会放到部署单元之中(如WAR文件),但是只有profile属性与当前激活profile相匹配的配置文件才会被用到。

    还可以在根<beans>元素中嵌套定义<beans>元素,而不是为每个环境都创建一个
profile XML文件。这能够将所有的profile bean定义 放到同一个XML文件中,
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans";
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
    xmlns:jdbc="http://www.springframework.org/schema/jdbc";
    xmlns:jee="http://www.springframework.org/schema/jee";
    xmlns:p="http://www.springframework.org/schema/p";
    xsi:schemaLocation="
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">;        

    <beans profile="dev">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:schema.sql" />
            <jdbc:script location="classpath:test-data.sql" />
        </jdbc:embedded-database>
    </beans>

    <beans profile="qa">
        <bean id="dataSource"
            class="org.apache.commons.dbcp.BasicDataSource"
            destroy-methd="close"
            p:url="jdbc:h2:tcp://dbserver/~/test"
            p:driverClassName="org.h2.Driver"
            p:username="sa"
            p:password="password"
            p:initialSize="20"
            p:maxActive="30" />
    </beans>

    <beans profile="prod">
        <jee:jndi-lookup id="dataSource"
                jndi-name="jdbc/myDatabase"
                resource-ref="true"
                proxy-interface="javax.sql.DataSource" />
    </beans>
</beans>       
    这里有三个bean,类型都 是javax.sql.DataSource,并且ID都是dataSource。但是在运行时,只会创建一个bean,这取决于处于激活状态的是哪个profile。

3.1.2 激活profile
    Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.activespring.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注解设置;
    
    在Web应用中,设置spring.profiles.default的web.xml文件会如下所示:
    为上下文设置默认的profile,为Servlet设置默认的profile
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
    xmlns="http://java.sun.com/xml/ns/javaee";
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">;

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>

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

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

    <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>
    
    <servlet-mapping>
        <servlet-name>appServlet</servlet>
        <url-pattern>/<url-pattern>
    </servlet-mapping>

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

使用profile进行测试
    当运行集成测试时,通常会希望采用与生产环境(或者是生产环境的部分子集)相同的配置进行测试。但是,如果配置中的bean定义在了 profile中,那么在运行测试时,我们就需要有一种方式来启用合适的 profile。
    Spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时要激活哪个profile。在集成测试时,通常想要激活的是开发环境的profile。例如,下面的测试类片段展现了使 用@ActiveProfiles激活dev profile:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={persistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest
{
    ...
}

原创粉丝点击