作者注:在《Better, Faster, Lighter Java》一书第八章的这份节录中,我们将看到一个使用Spring框架的企业web应用程序例子。尽管Hibernate提供了单个的服务,Spring框架却提供了一种高效地构建和汇编Java应用程序的方法,以及多种服务的抽象。尽管Spring支持多种服务,但是它最受关注也是最出色的特性是杰出的分层和封装。与EJB一样,Spring的中心组件是一个容器;而且Spring框架也同样提供对核心J2EE服务的访问。但是这就是它们仅有的相似之处了。下面是一个比喻。
我喜欢皮划艇运动,也花了很多时间来教授皮划艇技巧。我的一个专长就是教授学生如何在浪花中翻转弄翻了的皮划艇。一天,我向一位四肢发达的大个子和一位玲珑瘦小、体重只有97磅的女士传授该技巧。当我在陆地上从头到尾仔细地讲述这项技巧的时候,大个子直勾勾的盯着远方,不感兴趣。而那位女士注意力集中,而且希望反复练习这项基础技巧。在半个小时之内,她就出色地完成了第一次翻转,而他只是上下摇摆,在平静的水面上拍打出细小的白色泡沫。直到第三个学时,他才完成这个动作。在以后的几个学时中,她依靠技巧快速提高,而他却单凭力量在水中挣扎。到了实践的时候,她翻转了皮划艇,而他却在游泳。程序员们,请记住:解决问题的最佳方式是依靠简单性和技巧,而不是蛮力。
Pet Store:一个反例
Pet Store J2EE应用程序是一个很糟糕的编程例子。它误导成千上万名J2EE开发人员构建出设计糟糕、性能低下的代码。它还是一个基准的争论焦点。Middleware Company致力于制订比较J2EE和微软的。NET平台的基准。他们选择Pet Store的J2EE版本作为基准的基础。尽管他们很努力地调优,J2EE版本还是惨败给Microsoft .NET版本,因此导致J2EE设计备受批评。我不想针对这次惨败谴责什么。我有不同的看法。我坚持认为:利用J2EE(尤其是EJB)很难开发出整洁、高性能的代码。另一方面,Pet Store基准本身就会导致更大的问题。
基准纷争过后,很多人借助于更平易且更简单的技术来实现Pet Store.其中一种最强大且最简单的实现方法是Clinton Begin所使用的,他利用一个称为iBatis的DAO框架来代替全部的实体bean.Rod Johnson的团队将该应用程序转化为Spring,并且现在已经与Spring框架一起发布。以下是相关的一些细节:
Spring jPetStore应用程序包含在M4或更高版本的Spring中。
它是一种具有JDBC DAO层的数据驱动的应用程序。
它提供了Strut的替代前端以及Spring MVC框架。
它提供了两种不同的模型。最简单的模型使用单一数据库和简单JDBC事务。另一种模型可以跨多个数据库使用JTA事务管理。
在下面的部分中,我将演示具有MVC web前端和单个数据库中的简单事务的应用程序版本。我将重点讨论域模型、单数据库DAO层、单一事务以及Spring MVC前端。Spring网站上提供了大量的资源,可供希望深入研究的开发人员使用。
配置文件
了解Spring应用程序应当从配置文件开始,它显示了主要的bean以及应用程序如何将它们组合在一起。Spring配置文件在应用程序上下文中定义bean.将上下文看作是收集应用程序指定资源的一种便利方式。
很多J2EE应用程序借助单元素来了解诸如连接之类的应用程序资源。这种用途的单元素与很多Java开发人员经常使用的全局变量差别不大。J2EE中的替代方案是一种称为JNDI的目录服务,但是对于许多常见用例来说它就是杀鸡的牛刀了。而Spring使用一种应用程序上下文。最初,需要在一个简单的XML文件中指定应用程序上下文,尽管也可以通过扩展Spring来接受其它类型的配置文件。以下是应用程序上下文中可能会包含的内容:
数据源
管理连接的Java类,通常在一个池中。
DAO层
如果应用程序使用了数据库,那么很可能需要隔离对DAO层数据库的访问。可以通过应用程序上下文来访问该层。
持久性管理器
每个持久性框架都有一个应用程序用来访问其特性的对象或工厂。对于Hibernate来说,它就是会话和会话工厂。而对于JDO来说,它就是持久性管理器工厂和持久性管理器。
事务策略
可以显式地声明希望在事务中使用的方法以及用于实施该策略的事务管理器。
事务管理器
J2EE中有很多不同的事务管理策略。对于单一数据库的应用程序,Spring允许使用数据库的事务管理。对于多个数据库或事务源,Spring允许使用JTA.可以将事务管理器保存在应用程序上下文中。
验证逻辑
Spring框架使用一种与Strut类似的验证框架。Spring允许像配置其他业务组件那样配置验证逻辑。
视图和控制器
Spring框架允许为视图指定控制器,并帮助用户通过控制器来配置导航路径。
jPetStore应用程序使用包含一个数据源、DAO层和一种事务逻辑的Spring应用程序上下文。用户定义XML文档中上下文的内容,该XML文档列出了一系列bean.每一个XML配置文件都包含一个
我们来看一个更具体的实例。jPetStore应用程序为业务逻辑、数据层和用户界面提供了三种配置文件,每种Spring配置文件各自描述三者之一,如图8-2所示。
图8-2 jPetStore应用程序提供了分别与三个不同的层相匹配的Spring应用程序上下文
这些配置文件指定了域模型、数据层和表示层的上下文。例8-1展示了jPetStore应用程序的业务逻辑应用程序上下文的一部分。注意:为了简单起见,我将包名org.springframework.samples.jpetstore...缩写为jpetstore。
例8-1 applicationContext.xml
[1]
[2]
[3]
[4]
[5]
以下是对注释的解释:
[1]业务逻辑。这部分(包括所有粗体代码)包含核心的业务逻辑。验证和域模型都视为业务组件。
[2]验证器。这是Order的验证器。一旦用户提交Order格式,并发送到错误页面或order完成页面,Spring就会按照要求调用该验证器。
[3]核心业务实现。该类包含持久性域模型的核心实现。它包含以下所有的DAO对象。
[4]属性。每一个bean都具有自己的属性,这些属性引用定义在其他地方的bean。在本例中,bean属性是单个的DAO。每一个bean都定义在另一个Spring配置文件中。
[5]事务声明。这个bean指定了应用程序的事务策略。在本例中,应用程序使用在另一个Spring配置文件中指定的事务管理器。它声明了应当传播为事务的方法。例如,所有以insert开头的方法应当传播为事务。
简而言之,该配置文件就像一种粘合剂,将应用程序的业务逻辑粘合在一起。在文件中,您会看到一些对本身并未包含在配置文件中的bean(如:DAO对象)的引用。随后,您会看到其他两种配置文件,它们定义了一些遗漏的bean。其中一个配置文件指定了事务管理器的数据访问对象。另一个配置文件指定了用户界面所需的bean。最好将配置文件分离到不同的层中,这样就可以按照需要配置各个层。例如,可能需要更改用户界面的策略(比如从Spring MVC web更改为Strut),或者数据访问的策略(比如从具有单个数据库的DAO更改为具有JTA事务、跨两个数据库的DAO)。
如果需要实例化XML上下文文件中的bean,那么非常简单。例如,要访问context.xml文件中Customer类型的名称为myCustomer的bean,可以采用以下三个步骤:
InputStream stream = getClass( ).getResourceAsStream("context.xml");
XmlBeanFactory beanFactory = new XmlBeanFactory(stream);
Customer cust = (Customer)beanFactory.getBean(myCustomer);
或者,如果希望Spring初始化一个上下文,然后抓取会话外观,可以使用以下代码:
protected static final String CONTEXT_FILE = "WEB-INF/applicationContext.xml";
Biz biz; // session façade
FileSystemXmlApplicationContext ctx =
new FileSystemXmlApplicationContext(CONTEXT_FILE);
biz = (Biz) ctx.getBean("biz");
最好放开控制权。通常不必直接访问应用程序上下文。框架会执行该操作。例如,如果您正在使用servlet,那么Spring框架会为每一个servlet提供一个上下文,并为所有servlet提供一个总体上下文。通常可以从中获得正确的上下文信息,随后您就会看到这一点。既然已经看到了表示jPetStore应用程序的配置文件,现在应该看看如何构建各个元素:
域模型
与本书阐述的原理相对应,该应用程序以图8-3中的透明域模型为基础。该域模型包含表示现实世界的各个对象之间的业务关系。Pet Store由包含项目的cart(购物车)和order(订单)组成。
图8-3 应用程序的中心是域模型。
应用程序表示了一个简单的宠物商店。它由一个包含购物车项目(cart item)的购物车组成,该购物车又填充了一个包含线项目(line item)的订单。项目(item)由按类别(category)组织的产品(product)组成。每个对象都是一个透明的业务对象,被实现为具有一些属性和业务方法的Java bean。例8-2展示了一个CartItem。为了简单起见,我已经去掉导入和包细节。
例8-2CartItem.java
[1] public class CartItem implements Serializable {
/*Private Fields*/
private Item item;
private int quantity;
private boolean inStock;
/*JavaBeans Properties*/
[2] public boolean isInStock() { return inStock; }
public void setInStock(boolean inStock) { this.inStock = inStock; }
public Item getItem( ) { return item; }
public void setItem(Item item) {
this.item = item;
}
public int getQuantity( ) { return quantity; }
public void setQuantity(int quantity) {
this.quantity = quantity;
}
[3] public double getTotalPrice() {
if (item != null) {
return item.getListPrice( ) * quantity;
}
else {
return 0;
}
}
/*Public methods*/
public void incrementQuantity( ) {
quantity++;
}
}
以下是注释的含义:
[1]Spring框架不会强迫组件继承Spring类。它们是完全透明的,并且如果进行测试或者情况需要,还可以驻留在容器的外部。
[2]每一个字段都使用get和set方法进行包装,以便Spring可以通过Java反射来配置他们。(Spring还可以通过构造函数来配置它们。)
[3]与很多EJB应用程序不同,在域模型中包含业务域逻辑非常有用。
我将这种模型称作passive。它完全由域外面的对象调用,并且仅与域中的其他对象耦合。注意,虽然它具有私有属性和公有字段,但是它不仅仅是一个值对象。它包含用于计算总价和增加数量的业务方法。这种设计使得该业务对象易于理解和重用,即使是在整体设计改进的情况下。当我们介绍持久性方面时,您就会看到该模型的其他部分。