UML软件工程组织

轻量级开发成功秘诀:露出水面的Spring
作者:Bruce Tate
轻量级容器在为应用程序组织胶水代码方面,提供了一种方法。Spring 框架在轻量级容器世界独占鳌头。它内含了轻量级容器、面向方面编程框架和可以轻而易举整合数百种开放源码框架的胶水代码。

在 2003 年的时候,三个山地自行车手和我尖叫着冲下一条叫做 Forrest Ridge 的陡峭小径。这条小径因为刚刚重新开放,没有什么宣传,因此德克萨斯州首府奥斯汀只有很少的人知道它。整个秋天,我们都保守这个秘密,享受它带给我们的快乐。而到了今天,我们不得不在这条小径上躲避 20 个以上的车手。因为,秘密公开了!

尽管大家发现了这条奇妙的小径对于我和我的朋友而言不是好事,但是,当您的开放源码项目被人发现时,那却是一件令人欣喜若狂的事情。因为从此您就可以更易找到资源、更快修补项目缺陷,并且通常您还可以利用其他的社区资源。现在,Spring 已经被大家所发现。它是最重要的开放源码项目之一,在企业环境的输入输出中扮演了重要角色,以它自己的方式取得了与 Hibernate 类似的辉煌成果。本文向您展示了 Spring 对于轻量级的开发为何意义如此重大。

Spring 是什么?

您若是一名企业程序开发人员,Spring 会令您事半功倍。但它到底是什么?对于这样的综合性框架,很难轻易找到一个明确的答案。从本质上讲,Spring 是一个轻量级容器。您可以通过 Spring 来利用普通 Java™ 对象(POJO)编程,使用依赖注入解析 POJO 间的依赖性,然后使用面向方面编程(AOP)将服务与它们相关联。

Spring 也提供了许多胶水代码,这使您可以更加轻松地使用 Java 2 平台企业版(J2EE)服务,比如用于事务的 Java 事务 API (JTA)、用于远程控制的远程方法调用(RMI)、用于管理的 Java 管理扩展(JMX)或用于持久性的 Java 数据对象(JDO)。Spring 还为开放源码框架,比如 Hibernate、Tapestry、Struts 和 JavaServer Faces(JSF),提供了胶水代码。注意,虽然有些框架是相互竞争的,但这并不是什么问题,Spring 没有试图只支持一种获胜的框架。

使用 Spring,使您能够利用一些服务。例如,Web Flow 可以轻松地处理 Web 页面间的流。类似地,Web MVC 为类似 Struts 的基于 Web 的应用程序提供了模型-视图-控制器(MVC)架构。

因此,Spring 支持众多的技术。与企业 JavaBean(EJB)技术一样,Spring 容器让您可使用很多企业服务。但是与 EJB 1.x 和 2.x 不一样,放入容器的是 JavaBean,而不是一些私有组件。与 EJB 脱离的另一方面是,Spring 并不将您限制于少量标准服务,而是让您从大量服务中自由选择,甚至构建您自己的服务。

在我看来,Spring 是 Java 社区中最重要的开放源码项目之一。它帮助我们重新定义了 J2EE。在一定程度上由于来自 Spring 技术革新的压力,导致 EJB 3 专家小组构建了一个比 EJB 2.x 更加相似 Spring 的接口。我能够轻易地想像到,在企业级程序开发中,Spring 的使用会变得同 Struts 一样普及。为帮助您了解 Spring 如此重要的原因,让我们一层层剥开它神秘的面纱。

核心容器

首先,我们来看看 Spring 的工作原理。在此,我不会为您展示完整的例子,因为您可以找到数不清的 Spring 教程。我使用的是我最新出版的书中的示例应用程序,这里节选示例中的一个片段来向您展示 Spring 可以帮助您所做的事情。

这个应用程序简单地为一个称为 RentaBike 的操作维护一个自行车清单。Spring 是此应用程序的“中枢系统”。Spring 的主要容器 —— 上下文 —— 保存着对所有应用程序主要层和服务的引用。应用程序的主要层是数据库、持久性框架、数据库访问对象和用户界面(UI)控制器及视图层。

这个应用程序的 Spring 上下文只是应用程序使用的 bean 的清单。但是,这些 bean 不是必须依赖所有的 Spring 接口,我在这里选择依赖 Spring 是因为要为您展示一些重复的胶水代码(它们将应用程序联系到一起)。在此上下文中,您会发现至少五种类型的元数据:

  • 配置 —— 因为容器已经进行了一些配置来处理依赖注入(在 Secrets of lightweight development success, Part 2: How to lighten up your containers 中讲到过),这里有必要也处理其他配置,以便您可以拥有一个方便、一致的策略。
  • 应用程序的主要层 —— 在 RentaBike 应用程序中,我公开了一个数据访问对象(DAO)层、一个控制器层和一个视图层(参阅清单 1)。我选择将这个 DAO 封装在一个接口中,这样我就能够切换数据访问层来使用不同的持久性策略。
  • 外部依赖性 —— 例如,我的 DAO 需要一个数据源。Spring 会注入我的 DAO 需要的资源(比如数据源)和平台特定的配置(比如 Hibernate 的会话工厂)。
  • 透明服务 —— 我们从 EJB 技术中的最大获益之一就是声明性事务。但是您不得不花费大力气得到它们,因为您被强制使用 EJB 接口和重量级 EJB 容器。Spring 让您可以只配置您需要的服务并将它们应用于 POJO。在 Spring 中,您可以使任何方法成为事务型的。
  • 数据 —— 通常,消息数据、关于应用程序流的信息和测试数据也可能放置在应用程序上下文中。
清单 1. RentaBike 的一部分上下文

<beans>
 
 <bean id="rentaBike" class="com.springbook.JDBCRentABike">
   <property name="storeName">
     <value>Bruce's Bikes</value> 
   </property>
   <property name="dataSource">
     <ref bean="dataSource" /> 
   </property>
 </bean>
 


  <bean id="bikesController" class="com.springbook.BikesController">
    <property name="facade">
      <ref bean="rentaBike" /> 
    </property>
 


  </bean>
    <bean id="dataSource"
              class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName">
      <value>com.mysql.jdbc.Driver</value> 
    </property>
    <property name="url">
      <value>jdbc:mysql://localhost/bikestore</value> 
    </property>
    <property name="username">
      <value>bikestore</value> 
    </property>
  </bean>
 
</beans>


如果仔细观察上下文,您会注意到它是由 bean 组成的。这很重要,因为早期的 J2EE 容器强制您使用给定的 API,具体来说是 EJB。每个 bean 都有一套属性。一些属性是字符串和基本类型值。一些是 bean,比如 rentaBike bean 需要的数据源。您不必实现类似 EJB 会话 bean 接口一样的 API。您只要列出 bean 和它们的属性。其中一些属性满足了依赖性。您现在已经得到了较好的透明性,因此只要需要,您可以在容器之外运行您的 bean 或将您的应用程序移动到其他容器中。

用于测试的上下文

您还可以在上下文中包括其他类型的 bean。例如,使用上下文填充上下文中的简单测试数据或应用程序流 是非常有意义的。在 Spring: A Developer's Notebook 中,我们使用存根(它使用数组列表代替数据库)驱动第一个 UI 实现。假如,我们已经得到一个具有姓名和 email 地址的数据库表。我就可以构建一个简单的存根(具有作为属性的数组列表),然后快速实现一些简单数据访问方法,如清单 2 所示。


清单 2. 替换数据库的测试存根

public class StubEmailDirectory {
    private List people;
    public List getPeople() {
        return people;
    }
    public void setPeople(List people) {
        this.people = people;
    }
 
    public String findEmail(String name) {
        Person p = findPerson(name);
        if (p==null) {
             return null;
        }
        return p.getEmail();
    }
.
public Person findPerson(String name) {
        if (people==null) {
            return null;
        }
        int size = people.size();
        for(int i=0;i<size;i++) {
            Person p=(Person)people.get(i);
            if(p.getName().equals(name)) {
                 return p;
            }
        }
        return null;
    }
}


然后,我可以从上下文填充它,如清单 3 所示。


清单 3. 上下文中的测试数据

<beans>
 
<bean id="bruce" class="j2life.bus.Person">
        <property name="name">
            <value>Bruce</value>
        </property>
        <property name="email">
            <value>bruce.t@j2life.com
            </value>
        </property>
    </bean>
 
<bean id="maggie" class="j2life.bus.Person">
        <property name="name">
            <value>Maggie</value>
        </property>
        <property name="email">
            <value>maggie.t@j2life.com
            </value>
        </property>
    </bean>
    <bean id="emails" class="j2life.bus.StubEmailDirectory">
        <property name="people">
            <list>
                <ref bean="bruce"/>
                <ref bean="maggie"/>
            </list>
        </property>
    </bean>
</beans>

当您考虑测试时,从上下文中摘出测试数据常常很有意义。为使测试用例可重复和可验证,您可以从上下文而不是服务摘出不可预知的数据(例如,系统时间和随机数)。Spring 的上下文使测试更加容易,因为您可以通过不同方法为生产和测试配置应用程序。

Spring 的基础是容器,并且这个基础很重要。但容器远不能代表它的全部。

优势

同我一样,自行车世界冠军 Lance Armstrong 也来自德克萨斯首府奥斯汀。我很多年来,都很喜欢欣赏他的比赛,部分原因在于他从每次登车中得到的优势比世界上任何人得到的都要多。我寻找机会,以从我所做的每件事中得到附加的优势。Spring 通过提供胶水代码,消除麻烦重复的资源管理、配置和应用程序中的繁重工作,使我得到更多的优势。

采用持久性。Hibernate 是一套漂亮的持久性替换方案。但像所有持久性框架一样,它强制您管理一些低级的细节(我宁愿不要它们)。清单 4 展示了一个没有 Spring 的 Hibernate 应用程序示例。

清单 4. 没有 Spring 的 Hibernate

// Configuration code
Configuration config = new Configuration( );
config.addClass(Bike.class).addClass(Customer.class).
addClass(Reservation.class);
SessionFactory mySessionFactory = Configuration.buildSessionFactory( );

 
public List getBikesOldWay( ) throws Exception {
  List bikes = null;
  Session s = null;
  try {
    s = mySessionFactory.openSession( );
    bikes = s.find("from Bike");
  }catch (Exception ex) {
    //handle exception gracefully
  }finally {
    s.close( );
  }
  return bikes;
}

清单 5 展示了一个类似的示例,但它使用了 Spring。

清单 5. 使用 Spring 的 Hibernate

public List getBikes( ) {
  return getHibernateTemplate( ).find("from Bike");
}

可以将 Hibernate 模板看作是实现会话处理的方法。尤其要注意您所没有看到的。您不必担心事务,因为 Spring 让您通过声明配置它们。您也不必担心管理资源,因为代码是在模板中。Spring 总是会关闭会话,所以您不必担心。您不必在这个级别上处理异常,因为 Spring 将它们转换为一套通用的未检查异常(unchecked exceptions)。我喜欢未检查异常,因为我可以选择在架构的合适级别上抛出它们。只需提供很少信息来告诉 Hibernate 做什么。Spring 处理剩下的事情。这就是优势。

Spring 也通过其他途径建立了优势。通过为其他服务提供同种的胶水代码,Spring 减轻了应用程序编程人员的负担。我几乎再也不必像往常一样重复劳动。我可以更加简单地使用远程控制、事务、安全、持久性和 MVC 代码,因为 Spring 确实做到了“减负”。

在这里,我想指出一些不利之处。在我决定使用 Spring 的胶水代码替代自己的胶水代码时,我自己构建了 Spring 框架的依赖关系。通常,我认为付出这种代价非常值得。

AOP

在本系列的第 2 部分,我们了解到 AOP 可以将服务与 POJO 相关联以提供更佳的透明性。使用 AOP,可以从代码中消除许多横切关注点。所以,为什么不马上下载 AOP 框架并直接使用它呢?

事实上,一些团队就是这么做的。根据我的经验,技术在我们学会使用它之前就已经早早准备好了。当我看到中级和初级程序员使用 AOP 时,我联想到一个拥有强大工具的孩子。正视它:我们当中的绝大多数人在遇到 AOP 时,仍然还像个孩子。因为它使用了面向对象编程,所以我们必须花费一些时间积累相关知识以更有效地使用 AOP。

通过 Spring 的辅助训练,我能够使用 AOP。我可以使用一些预打包的方面并观察定义好场景的示例,这样我不需要冒任何风险就可以从 AOP 中收获更多。实际上,在过去可以经常看到类似的策略。Ada 编程语言支持封装,但只有受限的继承性。像 Microsoft® Windows® 编程模型一样的早期窗口环境内部具有 Window 对象、类似 Smalltalk 消息的事件甚至还有继承,但是公开了一个过程编程模型。我认为这种策略对 AOP 是同等重要的。

Spring 的重大意义

对于那些使用 Spring 的人,Spring 无疑是革命性的。它的一些简单思想引发了影响深远的结果:

  • Spring 有力地开拓了企业级服务容器。使您不再“隶属”于容器提供的 API 或服务。可以使用 Spring 上下文管理预打包的服务,整合第三方服务或自己编写服务。
  • Spring 在可测试性方面有了长足的进步。通过 Spring,能够使用依赖注入将模拟对象插入到难以接触的位置,在容器外运行对象(因为这些对象是 POJO),甚至使用容器提供不可预知的数据。
  • Spring 的胶水代码使得只要编写很少的代码就可以轻松插入企业服务。编写的代码越少,维护和扩展代码时工作量就越少。
  • Spring 代表一个开放源码项目,重新定义了 Java 商业小组开发软件的方式。Spring 对于最新 EJB 规范的影响是不容置疑的,同时也是非常重要的。

Spring 已经成为企业轻量级开发的顶梁柱之一。以下的参考资料为您展示如何入门和深入 Spring。在本系列的下一篇文章中,我会为您介绍轻量级开发中的一些替代的持久性方案。

所喜爱的开放源码“小径”也被大家所发现。


版权所有:UML软件工程组织