其实就算用Java建造一个不是很烦琐的web应用程序也不是件轻松的事情当为一个应用程序建造一个构架时有许多事情需要考虑从高层来说开发者需要考虑怎样建立用户接口(user interfaces)?在哪里处理业务逻辑?和怎样持久化应用数据这三层每一层都有它们各自的问题需要回答 各个层次应该使用什么技术?怎样才能把应用程序设计得松耦合和能灵活改变?构架允许层的替换不会影响到其它层吗?应用程序怎样处理容器级的服务(container level services)比如事务处理(transactions)?
当为你的web应用程序创建一个构架时需要涉及到相当多的问题幸运的是已经有不少开发者已经遇到过这类重复发生的问题并且建立了处理这类问题的框架一个好框架具备以下几点 减轻开发者处理复杂的问题的负担(不重复发明轮子)内部定义为可扩展的有一个强大的用户群支持框架通常能够很好的解决一方面的问题然而你的应用程序有几个层可能都需要它们各自的框架就如解决你的用户接口(UI)问题时你就不应该把事务逻辑和持久化逻辑掺杂进来例如你不应该在控制器(controller)里面写jdbc代码使它包含有业务逻辑这不是控制器应该提供的功能它应该是轻量级的代理来自用户接口(UI)外的调用请求给其它服务于这些请求的应用层好的框架自然的形成代码如何分布的指导更重要的是框架减轻开发者从头开始写像持久层这样的代码的痛苦使他们专注于对客户来说很重要的应用逻辑
这篇文章将讨论怎样组合几个着名的框架去做到松耦合的目的怎样建立你的构架怎样让你的各个应用层保持一致富于挑战的是组合这些框架使得每一层都以一种松耦合的方式彼此沟通而与底层的技术无关这篇文章将使用3种流行的开源框架来讨论组合框架的策略表现层我们将使用Struts();业务层我们将使用Spring();持久层使用Hibrenate()你也可以在你的应用程序中替换这些框架中的任何一种而得到同样的效果图1展示了当这些框架组合在一起时从高层看是什么样子
图1用Struts Spring 和 Hibernate框架构建的概览
应用程序的分层 (Application Layering)
大多数不复杂的web应用都能被分成至少4个各负其责的层次这些层次是表现层(presentation)持久层(persistence)业务层(business)领域模型层(domain model)每层在应用程序中都有明确的责任不应该和其它层混淆功能每一应用层应该彼此独立但要给他们之间放一个通讯接口让我们从审视各个层开始讨论这些层应该提供什么和不应该提供什么
表现层 (The Presentation Layer)
在一个典型的web应用的一端是表现层很多Java开发者也理解Struts所提供的然而太常见的是他们把像业务逻辑之类的耦合的代码放进了一个orgapachestrutsAction所以让我们在像Struts这样一个框架应该提供什么上取得一致意见这儿是Struts负责的
为用户管理请求和响应
提供一个控制器(controller)代理调用业务逻辑和其它上层处理
处理从其它层掷出给一个Struts Action的异常
为显示提供一个模型
执行用户接口(UI)验证
这儿是一些经常用Struts编写的但是却不应该和Struts表现层相伴的项目
直接和数据库通讯比如JDBC调用
业务逻辑和与你的应用程序相关的验证
事务管理
在表现层中引入这种代码将导致典型耦合(type coupling)和讨厌的维护
持久层 (The Persistence Layer )
在典型web应用的另一端是持久层这通常是使事情迅速失控的地方开发者低估了构建他们自己的持久层框架的挑战性一般来说机构内部自己写的持久层不仅需要大量的开发时间而且还经常缺少功能和变得难以控制有几个开源的对象-关系映射(ORM)框架非常解决问题尤其是Hibernate框架为java提供了"对象-关系持久化"(objecttorelational persistence)机制和查询服务Hibernate对那些已经熟悉了SQL和JDBC API的Java开发者有一个适中的学习曲线Hibernate持久对象是基于简单旧式Java对象(POJO)和Java集合(Java collections)此外使用Hibernate并不妨碍你正在使用的IDE下面的列表包含了你该写在一个持久层框架里的代码类型
查询相关的信息成为对象Hibernate通过一种叫作HQL的面向对象(OO)的查询语言或者使用条件表达式API(expressive criteria API)来做这个事情 HQL非常类似于SQL 只是把SQL里的table和columns用Object和它的fields代替有一些新的专用的HQL语言成分要学不过它们容易理解而且文档做得好HQL是一种使用来查询对象的自然语言花很小的代价就能学习它
保存更新删除储存在数据库中的信息
像Hibernate这样的高级对象-关系映射(objecttorelational mapping)框架提供对大多数主流SQL数据库的支持它们支持父/子(parent/child)关系事务处理继承和多态
这儿是一些应该在持久层里被避免的项目
业务逻辑应该在你的应用的一个高一些的层次里持久层里仅仅允许数据存取操作
你不应该把持久层逻辑(persistence logic)和你的表现层逻辑(presentation logic)搅在一起避免像JSPs或基于servlet的类这些表现层组件里的逻辑和数据存取直接通讯通过把持久层逻辑隔离进它自己的层应用程序变得易于修改而不会影响在其它层的代码例如Hebernate能够被其它持久层框架或者API代替而不会修改在其它任何层的代码
业务层(The Business Layer)
在一个典型的web应用程序的中间的组件是业务层或服务层从编码的视角来看这个服务层是最容易被忽视的一层不难在用户接口(UI)层或者持久层里找到散布在其中的这种类型的代码这不是正确的地方因为这导致了应用程序的紧耦合这样一来随着时间推移代码将很难维护幸好针对这一问题有好几种Frameworks存在在这个领域两个最流行的框架是Spring和PicoContainer它们叫作微容器(microcontainers)你可以不费力不费神的把你的对象连在一起所有这些框架都工作在一个简单的叫作依赖注入(dependency injection)(也通称控制反转(inversion of control))的概念上这篇文章将着眼于Spring的为指定的配置参数通过bean属性的setter注入(setter injection)的使用Spring也提供了一个构建器注入(constructor injection)的复杂形式作为setter注入的一个替代对象们被一个简单的XML文件连在一起这个XML文件含有到像事务管理器(transaction management handler)对象工厂(object factories)包含业务逻辑的服务对象(service objects)和数据存取对象(DAO)这些对象的引用(references)
这篇文章的后面将用例子来把Spring使用这些概念的方法说得更清楚一些业务层应该负责下面这些事情
处理应用程序的业务逻辑和业务验证
管理事务
预留和其它层交互的接口
管理业务层对象之间的依赖
增加在表现层和持久层之间的灵活性使它们互不直接通讯
从表现层中提供一个上下文(context)给业务层获得业务服务(business services )
管理从业务逻辑到持久层的实现
领域模型层 (The Domain Model Layer )
最后因为我们讨论的是一个不是很复杂的基于web的应用程序我们需要一组能在不同的层之间移动的对象领域对象层由那些代表现实世界中的业务对象的对象们组成比如一份订单(Order)订单项(OrderLineItem)产品(Product)等等这个层让开发者停止建立和维护不必要的数据传输对象(或者叫作DTOs)来匹配他们的领域对象例如Hibernate允许你把数据库信息读进领域对象(domain objects)的一个对象图这样你可以在连接断开的情况下把这些数据显示到UI层那些对象也能被更新和送回到持久层并在数据库里更新而且你不必把对象转化成DTOs因为DTOs在不同的应用层间移动可能在转换中丢失这个模型使得Java开发者自然地以一种面向对象的风格和对象打交道没有附加的编码
结合一个简单的例子
既然我们已经从一个高的层次上理解了这些组件 现在就让我们开始实践吧在这个例子中我们还是将合并StrutsSpringHibernate框架每一个这些框架在一篇文章中都有太多的细节覆盖到这篇文章将用一个简单的例子代码展示怎样把它们结合在一起而不是进入每个框架的许多细节示例应用程序将示范一个请求怎样跨越每一层被服务的这个示例应用程序的一个用户能保存一个订单到数据库中和查看一个在数据库中存在的订单进一步的增强可以使用户更新或删除一个存在的订单
你可以下载这个应用的源码()
因为领域对象(domain objects)将和每一层交互我们将首先创建它们这些对象将使我们定义什么应该被持久化什么业务逻辑应该被提供和哪种表现接口应该被设计然后我们将配置持久层和用Hibernate为我们的领域对象(domain objects)定义对象关系映射(objecttorelational mappings)然后我们将定义和配置我们的业务对象(business objects)在有了这些组件后我们就能讨论用Spring把这些层连在一起最后我们将提供一个表现层(presentation layer)它知道怎样和业务服务层(business service layer)交流和知道怎样处理从其它层产生的异常(exceptions)
领域对象层(Domain Object Layer)
因为这些对象将和所有层交互这也许是一个开始编码的好地方这个简单的领域模型将包括一个代表一份订单(order)的对象和一个代表一个订单项(line item for an order)的对象订单(order)对象将和一组订单项(a collection of line item)对象有一对多(onetomany)的关系例子代码在领域层有两个简单的对象
agleboOrderjava: 包括一份订单(oder)的概要信息
agleboOrderLineItemjava: 包括一份订单(order)的详细信息
考虑一下为你的对象选择包名它将反映你的应用程序是怎样分层的例如简单应用的领域对象(domain objects)可以放进aglebo包[译者注bobusiness object?]更多专门的领域对象将放入在aglebo下面的子包里业务逻辑在agleservice包里开始打包DAO对象放进agleservicedaohibernate包对于forms和actions的表现类(presentation classes)分别放入agleaction 和 agleforms包准确的包命名为你的类提供的功能提供一个清楚的区分使当故障维护时更易于维护和当给应用程序增加新的类或包时提供一致性
持久层配置(Persistence Layer Configuration)
用Hibernate设置持久层涉及到几个步骤第一步是进行配置持久化我们的领域业务对象(domain business objects )因为我们用于领域对象(domain objects )持久化的Hibernate和POJOs一起工作( 此句原文Since Hibernate works with POJOs we will use our domain objects for persistence)因此订单和订单项对象包括的所有的字段的都需要提供getter和setter方法订单对象将包括像ID用户名合计和订单项这样一些字段的标准的JavaBean格式的setter和getter方法订单项对象将同样的用JavaBean的格式为它的字段设置setter和getter方法
Hibernate在XML文件里映射领域对象到关系数据库订单和订单项对象将有两个映射文件来表达这种映射有像XDoclet()这样的工具来帮助这种映射Hibernate将映射领域对象到这些文件
Orderhbmxml
OrderLineItemhbmxml
你可以在WebContent/WEBINF/classes/com/meagle/bo目录里找到这些生成的文件配置Hibernate SessionFactory(_docs/api/net/sf/hibernate/l)使它知道是在和哪个数据库通信使用哪个数据源或连接池加载哪些持久对象SessionFactory提供的Session(_docs/api/net/sf/hibernate/l)对象是Java对象和像选取保存更新删除对象这样一些持久化功能间的翻译接口我们将在后面的部分讨论Hibernate操作Session对象需要的SessionFactory配置
业务层配置(Business Layer Configuration )
既然我们已经有了领域对象(domain objects)我们需要有业务服务对象来执行应用逻辑执行向持久层的调用获得从用户接口层(UI layer)的请求处理事务处理异常为了将所有这些连接起来并且易于管理我们将使用Spring框架的bean管理方面(bean management aspect)Spring使用控制反转(IoC)或者setter依赖注入来把这些对象连好这些对象在一个外部的XML文件中被引用控制反转是一个简单的概念它允许对象接受其它的在一个高一些的层次被创建的对象使用这种方法你的对象从必须创建其它对象中解放出来并降低对象耦合
这儿是个不使用IoC的对象创建它的从属对象( object creating its dependencies without IoC)的例子这导致紧的对象耦合
图2没有使用IoC的对象组织对象A创建对象B和C
这儿是一个使用IoC的例子它允许对象在一个高一些层次被创建和传进另外的对象所以另外的对象能直接使用现成的对象·[译者注另外的对象不必再亲自创建这些要使用的对象](allows objects to be created at higher levels and passed into objects so that they can use the implementations directly)
图3对象使用IoC组织对象A包含setter方法它们接受到对象B和C的接口这也可以用对象A里的接受对象B和C的构建器完成
建立我们的业务服务对象(Building Our Business Service Objects)
我们将在我们的业务对象中使用的setter方法接受的是接口这些接口允许对象的松散定义的实现这些对象将被设置或者注入在我们这个例子里我们将使我们的业务服务对象接受一个DAO去控制我们的领域对象的持久化当我们在这篇文章的例子中使用Hibernate( While the examples in this article use Hibernate)我们可以容易的转换到一个不同的持久框架的实现通知Spring使用新的实现的DAO对象你能明白编程到接口和使用依赖注入模式是怎样宽松耦合你的业务逻辑和你的持久化机制的
这儿是业务服务对象的接口它是一个DAO对象依赖的桩(Here is the interface for the business service object that is stubbed for a DAO object dependency: )
public interface IOrderService {
public abstract Order saveNewOrder(Order order)
throws OrderException
OrderMinimumAmountException;
public abstract List findOrderByUser(
String user)
throws OrderException;
public abstract Order findOrderById(int id)
throws OrderException;
public abstract void setOrderDAO(
IOrderDAO orderDAO);
}
注意上面的代码有一个为DAO对象准备的setter方法这儿没有一个getOrderDAO方法因为它不是必要的因为不太有从外面访问连着的OrderDAO对象的需要DAO对象将被用来和我们的持久层沟通我们将用Spring把业务服务对象和DAO对象连在一起因为我们编码到接口我们不会紧耦合实现
下一步是写我们的DAO实现对象因为Spring有内建的对Hibernate的支持这个例子DAO将继承HibernateDaoSupport(
orm/hibernate/support/Hibl)类这使得我们容易取得一个到HibernateTemplate(
orm/hibernate/Hl)类的引用HibernateTemplate是一个帮助类它能简化Hibernate Session的编码和处理HibernateExceptions这儿是DAO的接口
public interface IOrderDAO {
public abstract Order findOrderById(
final int id);
public abstract List findOrdersPlaceByUser(
final String placedBy);
public abstract Order saveOrder(
final Order order);
}
我们还有两个对象要和我们的业务层连在一起这包括HibernateSessionFactory和一个TransactionManager对象这在Spring配置文件里直接完成Spring提供一个HibernateTransactionManager(
orm/hibernate/HibernateTrl)它将从工厂绑定一个Hibernate Session到一个线程来支持事务(见ThreadLocal()获取更多的信息)这儿是HibernateSessionFactory和HibernateTransactionManager的Spring配置
class="org.springframework.orm.hibernate. LocalSessionFactoryBean">
com/meagle/bo/Order.hbm.xml
com/meagle/bo/OrderLineItem.hbm.xml
net.sf.hibernate.dialect.MySQLDialect
false
C:/MyWebApps/.../WEB-INF/proxool.xml
spring
class="org. springframework.
orm.
hibernate.
HibernateTransactionManager">
每一个对象能被Spring配置里的一个标记引用。TW.WINgWIT.cOM在这个例子里,bean “mySessionFactory”代表一个HibernateSessionFactory,bean “myTransactionManager”代表一个Hibernate transaction manager。注意transactionManger bean有一个叫作sessionFactory的属性元素。HibernateTransactionManager有一个为sessionFactory准备的setter和getter方法,它们是用来当Spring容器启动时的依赖注入。sessionFactory属性引用mySessionFactory bean。这两个对象现在当Spring容器初始化时将被连在一起。这种连接把你从为引用和创建这些对象而创建singleton对象和工厂中解放出来,这减少了你应用程序中的代码维护。mySessionFactory bean有两个属性元素,它们翻译成为mappingResources 和 hibernatePropertes准备的setter方法。通常,如果你在Spring之外使用Hibernate,这个配置将被保存在hibernate.cfg.xml文件中。不管怎样,Spring提供了一个便捷的方式--在Spring配置文件中合并Hibernate的配置。获得更多的信息查阅Spring API()。既然我们已经配置了我们的容器服务beans和把它们连在了一起,我们需要把我们的业务服务对象和我们的DAO对象连在一起。然后,我们需要把这些对象连接到事务管理器。
这是在Spring配置文件里的样子:
class="org. springframework.
transaction.
interceptor.
TransactionProxyFactoryBean">
PROPAGATION_REQUIRED,readOnly,-OrderException
PROPAGATION_REQUIRED,-OrderException
class="com. meagle.
service.
spring.
OrderServiceSpringImpl">
class="com. meagle.
service.
dao.
hibernate.
OrderHibernateDAO">
图4是我们已经连在一起的东西的一个概览。它展示了每个对象是怎样相关联的和怎样被Spring设置进其它对象中。把这幅图和示例应用中的Spring配置文件对比查看它们之间的关系。
图4:这是Spring怎样将在这个配置的基础上装配beans。
这个例子使用一个TransactionProxyFactoryBean,它有一个为我们已经定义了的事务管理者准备的setter方法。这是一个有用的对象,它知道怎样处理声明的事务操作和你的服务对象。你可以通过transactionAttributes属性定义事务怎样被处理,transactionAttributes属性为方法名定义模式和它们怎样参与进一个事务。获得更多的关于在一个事务上配置隔离层和提交或回滚查阅TransactionAttributeEditor(
transaction/interceptor/Transactiol)。
TransactionProxyFactoryBean(
transaction/interceptor/Transactionl)类也有一个为一个target准备的setter,target将是一个到我们的叫作orderTarget的业务服务对象的引用(a reference)。 orderTarget bean定义使用哪个业务服务对象并有一个指向setOrderDAO()的属性。orderDAO bean将居于这个属性中,orderDAO bean是我们的和持久层交流的DAO对象。
还有一个关于Spring和bean要注意的是bean能以两种模式工作。这两种模式被定义为singleton和prototype。一个bean默认的模式是singleton,意味着一个共享的bean的实例将被管理。这是用于无状态操作--像一个无状态会话bean将提供的那样。当bean由Spring提供时,prototype模式允许创建bean的新实例。你应当只有在每一个用户都需要他们自己的bean的拷贝时才使用prototype模式。
提供一个服务定位器(Providing a Service Locator)
既然我们已经把我们的服务和我们的DAO连起来了,我们需要把我们的服务暴露给其它层。通常是一个像使用Struts或Swing这样的用户接口层里的代码来使用这个服务。一个简单的处理方法是使用一个服务定位器模式的类从一个Spring上下文中返回资源。这也可以靠引用bean ID通过Spring来直接完成。
这儿是一个在Struts Action中怎样配置一个服务定位器的例子:
public abstract class BaseAction extends Action {
private IOrderService orderService;
public void setServlet(ActionServlet
actionServlet) {
super.setServlet(actionServlet);
ServletContext servletContext =
actionServlet.getServletContext();
WebApplicationContext wac =
WebApplicationContextUtils.
getRequiredWebApplicationContext(
servletContext);
this.orderService = (IOrderService)
wac.getBean("orderService");
}
protected IOrderService getOrderService() {
return orderService;
}
}
用户接口层配置 (UI Layer Configuration)
示例应用的用户接口层使用Struts框架。这儿我们将讨论当为一个应用分层时和Struts相关的部分。
让我们从在struts-config.xml文件里检查一个Action配置开始。
type="agle.action.SaveOrderAction"
name="OrderForm"
scope="request"
validate="true"
input="/NewOrder.jsp">
Save New Order
path="/NewOrder.jsp" scope="request"
type="agle.exception.OrderException"/>
path="/NewOrder.jsp" scope="request"
type="com.
meagle.
exception.
OrderMinimumAmountException"/>
SaveNewOrder Action被用来持久化一个用户从用户接口层提交的订单。这是一个典型的Struts Action;然而,注意这个action的异常配置。这些Exceptions为我们的业务服务对象也在Spring 配置文件(applicationContext-hibernate.xml)中配置了(在transactionAttributes属性里)。当这些异常被从业务层掷出我们能在我们的用户接口里恰当的处理它们。第一个异常,OrderException,当在持久层里保存订单对象失败时将被这个action使用。这将引起事务回滚和通过业务对象传递把异常传回给Struts层。OrderMinimumAmountException,在业务对象逻辑里的一个事务因为提交的订单达不到最小订单数量而失败也将被处理。然后,事务将回滚和这个异常能被用户接口层恰当的处理。
最后一个连接步骤是使我们的表现层和我们的业务层交互。这已经通过使用前面讨论的服务定位器来完成了。服务层充当一个到我们的业务逻辑和持久层的接口。这儿是 Struts中的SaveNewOrder Action可能怎样使用一个服务定位器调用一个业务方法:
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws java.lang.Exception {
OrderForm oForm = (OrderForm) form;
// Use the form to build an Order object that
// can be saved in the persistence layer.
// See the full source code in the sample app.
// Obtain the wired business service object
// from the service locator configuration
// in BaseAction.
// Delegate the save to the service layer and
// further upstream to save the Order object.
getOrderService().saveNewOrder(order);
oForm.setOrder(order);
ActionMessages messages = new ActionMessages();
messages.add(
ActionMessages.GLOBAL_MESSAGE,
new ActionMessage(
"message.order.saved.successfully"));
saveMessages(request, messages);
return mapping.findForward("success");
}
结论
这篇文章按照技术和架构覆盖了许多话题。从中而取出的主要思想是怎样更好的给你的应用程序分层:用户接口层、持久逻辑层、和其它任何你需要的应用层。这样可以解耦你的代码,允许添加新的代码组件,使你的应用在将来更易维护。这里覆盖的技术能很好的解决这类的问题。不管怎样,使用这样的构架可以让你用其他技术代替现在的层。