如何用Spring 3.1的Environment和Profile简化工作

由于项目很紧,我每周写一篇技术博客的目标在12月份恐怕难以完成。我的周末一共只有4天,一个漂亮的家庭工作环境,当然这是为了方便做些其他的事情,而不是为了整天泡在Hack News上。现在已经是中午了,我还有2个小时完成这篇博客。马上开始!

我编写或设计的软件通常需要部署在不同的环境,也需要使用不同的部署配置。这些部署配置大致可以分为以下几类:

  1. Java企业级容器(包括JBoss、WebLogic、Tomcat、Glassfish等)
  2. 独立运行的Java应用程序
  3. GUI应用程序
  4. 测试框架

本文暂不对GUI应用程序进行讨论,留到后续再说。对于其他的类型(容器、独立应用程序和测试框架)来说,代码往往是相同的。因此,在设计或者编码这类软件的时候,这一点非常关键。对于每种场景来说,我所写的代码需要完美运行在每一种场景中。

这是程序合格并且健壮的关键!这里的问题是,有一些资源的配置是与代码的运行平台息息相关的。在我编写单元测试的时候,我无法(其实我能,这么说是为了让你领会文章的主题)获得绑定到JNDI树上的数据源。而在容器中,我只需要遍历JNDI树,询问是否可以获取数据源即可。

另外,想Spring这样的框架也是鼓励这样的开发模式的,至少控制反转挺受欢迎。不同于直接用代码配置数据库和队列,Spring框架是在运行时注入这些对象的,生活依然美好。

你以为我的博客会把网上到处都是控制反转的示例代码再罗列一遍么?当然不是。看下面的代码:

public class BusinessClazz implements SomethingReallyImportant {
   private DataSource dataSource;
   @Required
   public void setDataSource(DataSource dataSource) {
       this.dataSource = dataSource;
   }
}

数据源是通过运行时注入获得的,BusinessClazz对数据源本身一无所知。我不是世上最聪明的人,但也肯定不是最笨的人。我是说,我曾经读过像《J2ee Development Without Ejb》和《Expert one-on-one J2EE design and development》这类的书,并且自以为已经理解了其中的内容。但这些都是给妹子们看的,不是么?开个玩笑。我在Spring中间了一个数据源实例,并注入到BusinessClazz实例中。

<bean name="myBusinessClazz">
  <property name="dataSource" ref="dataSource"/>
</bean>

现在,我的可以完全不考虑数据源的具体实现,仍能在所有的部署环境中运行。但对于数据源呢,应该如何配置才能保证“一次配置,到处运行”?我们将注重对数据源的配置做说明,这个例子也是用于那些因运行环境或运行时不同而需要做修改的组件。

一般情况下,数据源需要两部分配置信息。第一部分是关于数据库位于何处,以及如何连接该数据库的信息。这里需要的信息包括主机名、款口端口号、服务名等。

第二部分是关于如何展示描述配置信息的。下面是一些可选方案。在其中,我建立了一个到数据库的连接,

<bean id="dataSource" destroy-method="close" 
   class="org.apache.commons.dbcp.BasicDataSource">
   <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
   <property name="url" value="jdbc:hsqldb:hsql://localhost"/>
   <property name="username" value="sa"/>
   <property name="password" value=""/>
</bean>

也可以使用Apache的commons-dbcp库创建一个连接池,

<bean id="dataSource"  class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
 <property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver"/>
 <property name="url" value="jdbc:myserver"/>
 <property name="username" value="username"/>
 <property name="password" value="password"/>
 <property name="initialSize" value="2"/>
 <property name="maxActive" value="5"/>
 <property name="maxIdle" value="2"/>
</bean>

还可以使用容器来管理连接,再从容器中获取连接,

<jee:jndi-lookup id="dataSource" jndi-name="java:mydatasource"/>


当然,还有其他许多方法可以配置数据源,但为了说明问题,上面的示例已经够用了。Spring框架的PropertyPlaceholderConfigurer类可以用来处理随运行环境而变化的属性,使用简单,功能强大。

现在的问题是,如何在不同的部署环境中无缝切换,并选择不同类型的数据源呢?

自己搞定

本文所说的问题并不起最近才有的,在过去的6、7年中,许多部署团队想出了各种不同的方法来解决它。每个团队都针对其特定的问题,采取了适合自身条件的方法来解决,他们会在代码中加入一些自适应的判断来简化问题。

这里,希望代码可以“一次编写,到处运行”的最大驱动力之一,就是可以开发人员编写用于测试生还 生产环境代码的单元测试。

对这个问题的一个典型的示例方案是将数据源定义在不同上下文文件中,这些文件需要按照统一的命名约定来命名,以免造成混乱。例如,假设我们有3个操作模式,所以需要建3个不同的上下文文件,

  • datasources-containerContext.xml
  • datasources-pooledConnection.xml
  • datasources-singleConnection.xml


接下来就是平台相关的代码了,会在你的应用程序初始化Spring的时候执行。它通过Ant式的通配符跨越部署的类路径来查找上下文文件。

ClassPathXmlApplicationContext containerContext = 
  new ClassPathXmlApplicationContext("**/**-containerContext.xml");
ClassPathXmlApplicationContext nonContainerContext = 
  new ClassPathXmlApplicationContext("**/**-pooledContext.xml");
ClassPathXmlApplicationContext testingContextContext = 
  new ClassPathXmlApplicationContext("**/**-singleContext.xml");


好了,在刚刚过去的几个小时里,你已经建立一个可以在多种环境中运行的系统,所编写的代码也充分利用这个特点。这个办法你已经用了好几年了,但是,这个办法的主要缺点是,每个团队、每个项目都会有自己的方式来解决前面提到的问题。

使用Spring Profile

Spring 3.1为这个问题提供了一个解决方案(如果你还没有为自己的项目升级Spring版本,嗯,你麻烦大了)。

Spring在容器中引入Environment和Profile的概念。每个应用程序上下文都有一个都可以访Environment对象。

ClassPathXmlApplicationContext classPathXmlApplicationContext = 
   new ClassPathXmlApplicationContext();
ConfigurableEnvironment configurableEnvironment = 
   classPathXmlApplicationContext.getEnvironment();

每种运行环境都有很多活动Profile类可供使用。大多数讲解Spring Profile的例子都是在开发模式或生产模式下。对于不同运行环境问题来说,我的解决方案是使用使用多个Profile来适应不同运行时。这个解决方案的优势是你可以自行决定如何使用Profile。

默认星空情况下,你所创建的Bean在载入容器中后是没有Profile对象的。下面看一个例子。假设下面是我的应用程序中,数据源实例的定义。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="…">
<bean id="dataSource" destroy-method="close" 
  class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
  <property name="url" value="jdbc:hsqldb:hsql://localhost"/>
  <property name="username" value="sa"/>
  <property name="password" value=""/>
</bean>
</beans>

在Spring 3.0中,增加了一个新的容器类GenericXmlApplicationContext ,可以作为ClassPathXmlApplicationContext和FileSystemXmlApplicationContext之外的另一个选择。

GenericXmlApplicationContext类的特点是可以通过Setter方法完成所有的配置,而无需依靠笨重的构造器去完成配置。记住,在初始化容器的准备工作完成后,需要调用refresh()方法完成实际的初始化工作。

下面的代码展示了如何使用GenericXmlApplicationContext类初始化容器:

GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.getEnvironment().setActiveProfiles("standalone");
ctx.load("*Context.xml");
ctx.refresh();


这里,我将活动Profile设置为“standalone”。在这个工程里,我希望代码既可以作为“standalone”运行在应用程序容器之外,还可以作为“container”运行在容器中。这里,我可以设置多个Profile,例如,下面的代码设置了Profile为“standalone”与“activemq”。

ctx.getEnvironment().setActiveProfiles("standalone", "activemq");

虽然做了上面的配置,实际上并不会对当前的配置上下文产生影响,因为还没有配置Profile实例。所以,修改配置上下文为:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="…" profile="standalone">
 <bean id="dataSource" destroy-method="close" 
  class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
  <property name="url" value="jdbc:hsqldb:hsql://localhost"/>
  <property name="username" value="sa"/>
  <property name="password" value=""/>
</bean>
</beans>

只有当活动Profile设置为“standalone”时,才会实例化这个Bean。Profile是Bean的属性,而不是一个实例对象,因此,你无法配置单独的Bean来选择Profile。在较早的Spring版本中,这会导致产生多个文件,而Ant的通配符无法在运行时找到正确的配置文件。

在Spring 3.1中,<beans/>标签可以嵌套在<beans/>标签内。现在,我们重新编写一下数据源配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="…">
<beans profile="standalone">  
<bean id="dataSource"> 
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/> 
<property name="url" value="jdbc:hsqldb:hsql://localhost"/> 
<property name="username" value="sa"/> 
<property name="password" value=""/> 
</bean> 
</beans> 

<beans profile="container">
<jee:jndi-lookup id="dataSource" jndi-name="java:mydatasource"/>
</beans>
</beans>

这样,就可以通过下面的代码快速切换Profile:

ctx.getEnvironment().setActiveProfiles("container");

另一种切换Profile的方法是在运行时作为系统参数传入:

-Dspring.profiles.active="standalone"

此外,也可以作为Ear/War的初始化参数传入:

<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
  org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
  <param-name>spring.profiles.active</param-name>
  <param-value>production</param-value>
</init-param>
</servlet>

阅读本文对你有什么帮助?

希望本文能够使你对Spring 3.1的新特性有所了解,这个新特性已经在Spring的3个主修订版中跳票了。

 

英文原文:Development Blog,翻译:ImportNew - 曹旭东

译文链接: http://www.importnew.com/1099.html

【如需转载,请在正文中标注并保留原文链接、译文链接和译者等信息,谢谢合作!】

关于作者: 曹旭东

码农一枚,热爱技术的技术菜鸟

查看曹旭东的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部