在前面一章,我们学习了基本的Bean装配技术。你可能会发现你在这一章学到了很多有用的东西,但是,
Bean装配比我们在第二章探讨的要多,Spring有一些其他更高级的Bean装配。
在这一章,我们学深入学习这些高级技巧。你不会每天使用这些技巧,但是这不代表这些高级技巧是没用
的。
开发软件的一个最大的挑战之一就是从开发换件到其他环境的切换。经常,在开发环境可以很好运行的软
件,到其他环境就不能使用了。数据库配置、加密算法和外部系统的继承只是总舵可能会改变整个部署环境
的事情中的一个。
比如,数据库配置。可能你在开发环境中使用一个嵌入数据库去测试。比如,你在Spring的配置文件中,你
会使用EmbeddedDatabaseBuilder在一个Bean声明中,如下:
@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安装了一个嵌入式的数据库,数据的schema被定义在文件schema.sql中,而
数据库的数据从test-data.sql中加载。
当在开发环境中进行手动测试的时候,这个dataSource是相当有用的。你可以规定每次打开的时候,你的数
据库都是在一个给定的状态。
尽管对于开发环境来说,这个dataSource是非常完美的。但是对于产品环境却是一个糟糕的选择。在产品环
境中,你可能希望通过JNDI去获取一个dataSource。在这种情况下,下面的Bean是一个更好的选择:
@Bean
public DataSource dataSource2() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
从JNDI检索数据源允许你的容器决定如何处理它的创建,包括从一个容器的数据源管理连接池里面移交。即
便如此,使用JNDI管理数据源更适合生成环境兵器避免了开发测试和集成测试倒置的不必要的复杂。
同时,在一个QA环境中,你可能选择一个完全不同的数据源,比如使用DBCP连接池。如下:
@Bean
public DataSource dataSource3() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUsername("sa");
dataSource.setPassword("password");
dataSource.setInitialSize(20);
return dataSource;
}
显然,这三个dataSource方法是各不相同的。他们都产生了一个javax.sql.DataSource的Bean,每一个都应
用了完全不同的策略。
这里,不去讨论怎么配置DataSource,但是,无疑,像DataSource看起来这么简单的Bean不是简单的。它
可能是一个关于如何在不同环境下面使用不同的Bean的很好的例子。
其中一种方式就是将不同的DataSource放在不同的配置文件中,然后使用maven的profile在编译器决定何
时使用哪一种配置方式。这个问题是,它需要应用程序去重新编译每一个环境。对于从开发到QA,重新编译
不是一个大的问题。但是,在QA到生成环境中要求一个重新编译可能会引入一些BUG,从而导致QA团队的
困难。
幸运的是,Spring提供了一种方式来做这个事情。
Spring对于环境相关的Bean提供的解决方案具有很多的不同。通常,环境相关意味着是去决定哪个Bean需
要创建而另外一个Bean不需要创建。但是不同于在编译决定去决定,Spring提供的是在运行期去决定。所
以,相同的部署包可能在所有的环境中运行。
在3.1版本中,Spring引入了profile。为了使用profile,你必须将所有的不同的Bean定义到一个或者多个
profile中,然后,确保在适当的环境中使用适当的配置文件。
在JavaConfig中,你可以使用@Profile注解去标明一个Bean属于哪个profile。比如,上面的DataSource可
能像下面这样定义。
package com.myapp;
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;
import javax.sql.DataSource;
/** * Created by LQ-ZHANGHUAI on 2016/3/14. */
@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")
.build();
}
}
我需要你注意的最重要的事情就是,@Profile注解是作用于类的。它告诉Spring,该类的所有Bean只有在
dev profile中才会被激活。如果dev profile没有被激活,则该类下面的Bean会被忽略。
同时,你可能有另外的生成环境配置文件类。如下:
package com.myapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jndi.JndiObjectFactoryBean;
import javax.sql.DataSource;
/** * Created by LQ-ZHANGHUAI on 2016/3/14. */
@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();
}
}
在这里,bean不会被创建,除非prod profile激活。
在Spring3.1中,你仅仅可以使用@Profile在类级别,但是从Spring3.2开始,你可以使用@Profile在方法级别,这个就使得将所有的Bean定义组合到一个文件中的可能,如下:
package com.myapp;
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;
import org.springframework.jndi.JndiObjectFactoryBean;
import javax.sql.DataSource;
/** * Created by LQ-ZHANGHUAI on 2016/3/14. */
@Configuration
public class DataSourceConfig {
@Bean(destroyMethod = "shutdown")
@Profile("dev")
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.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.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
在这个配置文件中的Bean,只有在它的profile激活的时候才会被创建。但是如果没有被@Profile注解注释的
@Bean,将始终会被创建。
你也可以在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.springframework.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创建xml配置文件。
当然,跟JavaConfig一样,你也可以将这些配置文件组织在一起,不过需要在beans下面嵌套一个beans元
素,如下所示:
<?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/jdbc/spring-jdbc.xsd 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-method="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>
这个就是JavaConfig一样,只有在特定的profile被激活的时候,该Bean才会被创建。
但是,怎么样让一个profile处于激活状态呢?