使用struts+spring+hibernate 组装web应用
摘要:
这篇文章将讨论怎样组合几个著名的框架去做到松耦合的目的,怎样建立你的构架,怎样让你的各个应用层保持一致。富于挑战的是:组合这些框架使得每一层都以一种松耦合的方式彼此沟通,而与底层的技术无关。这篇文章将使用3种流行的开源框架来讨论组合框架的策略
其实,就算用Java建造一个不是很烦琐的web应用程序,也不是件轻松的事情。当为一个应用程序建造一个构架时有许多事情需要考虑。从高层来说,开发者需要考虑:怎样建立用户接口?在哪里处理业务逻辑?和怎样持久化应用数据。这三层每一层都有它们各自的问题需要回答。 各个层次应该使用什么技术?怎样才能把应用程序设计得松耦合和能灵活改变?构架允许层的替换不会影响到其它层吗?应用程序怎样处理容器级的服务,比如事务处理?
当为你的web应用程序创建一个构架时,需要涉及到相当多的问题。幸运的是,已经有不少开发者已经遇到过这类重复发生的问题,并且建立了处理这类问题的框架。一个好框架具备以下几点: 减轻开发者处理复杂的问题的负担(“不重复发明轮子”);内部定义为可扩展的;有一个强大的用户群支持。框架通常能够很好的解决一方面的问题。然而,你的应用程序有几个层可能都需要它们各自的框架。就如解决你的用户接口(UI)问题时你就不应该把事务逻辑和持久化逻辑掺杂进来。例如,你不应该在控制器里面写jdbc代码,使它包含有业务逻辑,这不是控制器应该提供的功能。它应该是轻量级的,代理来自用户接口(UI)外的调用请求给其它服务于这些请求的应用层。好的框架自然的形成代码如何分布的指导。更重要的是,框架减轻开发者从头开始写像持久层这样的代码的痛苦,使他们专注于对客户来说很重要的应用逻辑。
这篇文章将讨论怎样组合几个著名的框架去做到松耦合的目的,怎样建立你的构架,怎样让你的各个应用层保持一致。富于挑战的是:组合这些框架使得每一层都以一种松耦合的方式彼此沟通,而与底层的技术无关。这篇文章将使用3种流行的开源框架来讨论组合框架的策略。表现层我们将使用Struts;业务层我们将使用Spring;持久层使用Hibrenate.你也可以在你的应用程序中替换这些框架中的任何一种而得到同样的效果。图1展示了当这些框架组合在一起时从高层看是什么样子。
图1用Struts, Spring, 和 Hibernate框架构建的概览
应用程序的分层
大多数不复杂的web应用都能被分成至少4个各负其责的层次。这些层次是:表现层、持久层、业务层、领域模型层。每层在应用程序中都有明确的责任,不应该和其它层混淆功能。每一应用层应该彼此独立但要给他们之间放一个通讯接口。让我们从审视各个层开始,讨论这些层应该提供什么和不应该提供什么。
表现层
在一个典型的web应用的一端是表现层。很多Java开发者也理解Struts所提供的。然而,太常见的是,他们把像业务逻辑之类的耦合的代码放进了一个org.apache.struts.Action。所以,让我们在像Struts这样一个框架应该提供什么上取得一致意见。这儿是Struts负责的:
为用户管理请求和响应;
提供一个控制器代理调用业务逻辑和其它上层处理;
处理从其它层掷出给一个Struts Action的异常;
为显示提供一个模型;
执行用户接口验证。
这儿是一些经常用Struts编写的但是却不应该和Struts表现层相伴的项目:
直接和数据库通讯,比如JDBC调用;
业务逻辑和与你的应用程序相关的验证;
事务管理;
在表现层中引入这种代码将导致典型耦合和讨厌的维护。
持久层
在典型web应用的另一端是持久层。这通常是使事情迅速失控的地方。开发者低估了构建他们自己的持久层框架的挑战性。一般来说,机构内部自己写的持久层不仅需要大量的开发时间,而且还经常缺少功能和变得难以控制。有几个开源的“对象-关系映射”框架非常解决问题。尤其是,Hibernate框架为java提供了"对象-关系持久化"机制和查询服务。Hibernate对那些已经熟悉了SQL和JDBC API的Java开发者有一个适中的学习曲线。Hibernate持久对象是基于简单旧式Java对象和Java集合。此外,使用Hibernate并不妨碍你正在使用的IDE。下面的列表包含了你该写在一个持久层框架里的代码类型:
查询相关的信息成为对象。Hibernate通过一种叫作HQL的面向对象的查询语言或者使用条件表达式API来做这个事情。 HQL非常类似于SQL-- 只是把SQL里的table和columns用Object和它的fields代替。有一些新的专用的HQL语言成分要学;不过,它们容易理解而且文档做得好。HQL是一种使用来查询对象的自然语言,花很小的代价就能学习它。
保存、更新、删除储存在数据库中的信息。
像Hibernate这样的高级“对象-关系”映射框架提供对大多数主流SQL数据库的支持,它们支持“父/子”关系、事务处理、继承和多态。
这儿是一些应该在持久层里被避免的项目:
业务逻辑应该在你的应用的一个高一些的层次里。持久层里仅仅允许数据存取操作。
你不应该把持久层逻辑和你的表现层逻辑搅在一起。避免像JSPs或基于servlet的类这些表现层组件里的逻辑和数据存取直接通讯。通过把持久层逻辑隔离进它自己的层,应用程序变得易于修改而不会影响在其它层的代码。例如:Hebernate能够被其它持久层框架或者API代替而不会修改在其它任何层的代码。
业务层
在一个典型的web应用程序的中间的组件是业务层或服务层。从编码的视角来看,这个服务层是最容易被忽视的一层。不难在用户接口层或者持久层里找到散布在其中的这种类型的代码。这不是正确的地方,因为这导致了应用程序的紧耦合,这样一来,随着时间推移代码将很难维护。幸好,针对这一问题有好几种Frameworks存在。在这个领域两个最流行的框架是Spring和PicoContainer,它们叫作微容器,你可以不费力不费神的把你的对象连在一起。所有这些框架都工作在一个简单的叫作“依赖注入”(也通称“控制反转”)的概念上。这篇文章将着眼于Spring的为指定的配置参数通过bean属性的setter注入的使用。Spring也提供了一个构建器注入的复杂形式作为setter注入的一个替代。对象们被一个简单的XML文件连在一起,这个XML文件含有到像事务管理器、对象工厂、包含业务逻辑的服务对象、和数据存取对象这些对象的引用。
这篇文章的后面将用例子来把Spring使用这些概念的方法说得更清楚一些。业务层应该负责下面这些事情:
处理应用程序的业务逻辑和业务验证;
管理事务;
预留和其它层交互的接口;
管理业务层对象之间的依赖;
增加在表现层和持久层之间的灵活性,使它们互不直接通讯;
从表现层中提供一个上下文给业务层获得业务服务;
管理从业务逻辑到持久层的实现。
领域模型层
最后,因为我们讨论的是一个不是很复杂的、基于web的应用程序,我们需要一组能在不同的层之间移动的对象。领域对象层由那些代表现实世界中的业务对象的对象们组成,比如:一份订单、订单项、产品等等。这个层让开发者停止建立和维护不必要的数据传输对象(或者叫作DTOs),来匹配他们的领域对象。例如,Hibernate允许你把数据库信息读进领域对象的一个对象图,这样你可以在连接断开的情况下把这些数据显示到UI层。那些对象也能被更新和送回到持久层并在数据库里更新。而且,你不必把对象转化成DTOs,因为DTOs在不同的应用层间移动,可能在转换中丢失。这个模型使得Java开发者自然地以一种面向对象的风格和对象打交道,没有附加的编码。
结合一个简单的例子
既然我们已经从一个高的层次上理解了这些组件, 现在就让我们开始实践吧。在这个例子中,我们还是将合并Struts、Spring、Hibernate框架。每一个这些框架在一篇文章中都有太多的细节覆盖到。这篇文章将用一个简单的例子代码展示怎样把它们结合在一起,而不是进入每个框架的许多细节。示例应用程序将示范一个请求怎样跨越每一层被服务的。这个示例应用程序的一个用户能保存一个订单到数据库中和查看一个在数据库中存在的订单。进一步的增强可以使用户更新或删除一个存在的订单。
因为领域对象将和每一层交互,我们将首先创建它们。这些对象将使我们定义什么应该被持久化,什么业务逻辑应该被提供,和哪种表现接口应该被设计。然后,我们将配置持久层和用Hibernate为我们的领域对象定义“对象-关系”映射。然后,我们将定义和配置我们的业务对象。在有了这些组件后,我们就能讨论用Spring把这些层连在一起。最后,我们将提供一个表现层,它知道怎样和业务服务层交流和知道怎样处理从其它层产生的异常。
领域对象层
因为这些对象将和所有层交互,这也许是一个开始编码的好地方。这个简单的领域模型将包括一个代表一份订单的对象和一个代表一个订单项的对象。订单对象将和一组订单项对象有一对多的关系。例子代码在领域层有两个简单的对象:
com.meagle.bo.Order.java: 包括一份订单的概要信息;
com.meagle.bo.OrderLineItem.java: 包括一份订单的详细信息;
考虑一下为你的对象选择包名,它将反映你的应用程序是怎样分层的。例如:简单应用的领域对象可以放进com.meagle.bo包。更多专门的领域对象将放入在com.meagle.bo下面的子包里。业务逻辑在com.meagle.service包里开始打包,DAO对象放进com.meagle.service.dao.hibernate包。对于forms和actions的表现类分别放入com.meagle.action 和 com.meagle.forms包。准确的包命名为你的类提供的功能提供一个清楚的区分,使当故障维护时更易于维护,和当给应用程序增加新的类或包时提供一致性。
持久层配置
用Hibernate设置持久层涉及到几个步骤。第一步是进行配置持久化我们的领域业务对象。因为我们用于领域对象持久化的Hibernate和POJOs一起工作,因此,订单和订单项对象包括的所有的字段的都需要提供getter和setter方法。订单对象将包括像ID、用户名、合计、和订单项这样一些字段的标准的JavaBean格式的setter和getter方法。订单项对象将同样的用JavaBean的格式为它的字段设置setter和getter方法。
Hibernate在XML文件里映射领域对象到关系数据库。订单和订单项对象将有两个映射文件来表达这种映射。有像XDoclet这样的工具来帮助这种映射。Hibernate将映射领域对象到这些文件:
Order.hbm.xml
OrderLineItem.hbm.xml
你可以在WebContent/WEB-INF/classes/com/meagle/bo目录里找到这些生成的文件。配置Hibernate SessionFactory使它知道是在和哪个数据库通信,使用哪个数据源或连接池,加载哪些持久对象。SessionFactory提供的Session对象是Java对象和像选取、保存、更新、删除对象这样一些持久化功能间的翻译接口。我们将在后面的部分讨论Hibernate操作Session对象需要的SessionFactory配置。
业务层配置
既然我们已经有了领域对象,我们需要有业务服务对象来执行应用逻辑、执行向持久层的调用、获得从用户接口层的请求、处理事务、处理异常。为了将所有这些连接起来并且易于管理,我们将使用Spring框架的bean管理方面。Spring使用“控制反转”,或者“setter依赖注入”来把这些对象连好,这些对象在一个外部的XML文件中被引用。“控制反转”是一个简单的概念,它允许对象接受其它的在一个高一些的层次被创建的对象。使用这种方法,你的对象从必须创建其它对象中解放出来并降低对象耦合。
这儿是个不使用IoC的对象创建它的从属对象的例子,这导致紧的对象耦合:
图2:没有使用IoC的对象组织。对象A创建对象B和C。
这儿是一个使用IoC的例子,它允许对象在一个高一些层次被创建和传进另外的对象,所以另外的对象能直接使用现成的对象·[译者注:另外的对象不必再亲自创建这些要使用的对象]:
图3:对象使用IoC组织。对象A包含setter方法,它们接受到对象B和C的接口。这也可以用对象A里的接受对象B和C的构建器完成。
建立我们的业务服务对象
我们将在我们的业务对象中使用的setter方法接受的是接口,这些接口允许对象的松散定义的实现,这些对象将被设置或者注入。在我们这个例子里我们将使我们的业务服务对象接受一个DAO去控制我们的领域对象的持久化。当我们在这篇文章的例子中使用Hibernate,我们可以容易的转换到一个不同的持久框架的实现,通知Spring使用新的实现的DAO对象。你能明白编程到接口和使用“依赖注入”模式是怎样宽松耦合你的业务逻辑和你的持久化机制的。
这儿是业务服务对象的接口,它是一个DAO对象依赖的桩。
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类,这使得我们容易取得一个到HibernateTemplate类的引用,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,它将从工厂绑定一个Hibernate Session到一个线程来支持事务。这儿是HibernateSessionFactory和HibernateTransactionManager的Spring配置。
<bean id="mySessionFactory"
class="org.springframework.orm.hibernate.
LocalSessionFactoryBean">
<property name="mappingResources">
<list>
<value>
com/meagle/bo/Order.hbm.xml
</value>
<value>
com/meagle/bo/OrderLineItem.hbm.xml
</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
net.sf.hibernate.dialect.MySQLDialect
</prop>
<prop key="hibernate.show_sql">
false
</prop>
<prop key="hibernate.proxool.xml">
C:/MyWebApps/.../WEB-INF/proxool.xml
</prop>
<prop key="hibernate.proxool.pool_alias">
spring
</prop>
</props>
</property>
</bean>
<!-- Transaction manager for a single Hibernate
SessionFactory (alternative to JTA) -->
<bean id="myTransactionManager"
class="org.
springframework.
orm.
hibernate.
HibernateTransactionManager">
<property name="sessionFactory">
<ref local="mySessionFactory"/>
</property>
</bean> |
每一个对象能被Spring配置里的一个<bean>标记引用。在这个例子里,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的配置。
既然我们已经配置了我们的容器服务beans和把它们连在了一起,我们需要把我们的业务服务对象和我们的DAO对象连在一起。然后,我们需要把这些对象连接到事务管理器。
这是在Spring配置文件里的样子:
<!-- ORDER SERVICE -->
<bean id="orderService"
class="org.
springframework.
transaction.
interceptor.
TransactionProxyFactoryBean">
<property name="transactionManager">
<ref local="myTransactionManager"/>
</property>
<property name="target">
<ref local="orderTarget"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="find*">
PROPAGATION_REQUIRED,readOnly,-OrderException
</prop>
<prop key="save*">
PROPAGATION_REQUIRED,-OrderException
</prop>
</props>
</property>
</bean>
<!-- ORDER TARGET PRIMARY BUSINESS OBJECT:
Hibernate implementation -->
<bean id="orderTarget"
class="com.
meagle.
service.
spring.
OrderServiceSpringImpl">
<property name="orderDAO">
<ref local="orderDAO"/>
</property>
</bean>
<!-- ORDER DAO OBJECT -->
<bean id="orderDAO"
class="com.
meagle.
service.
dao.
hibernate.
OrderHibernateDAO">
<property name="sessionFactory">
<ref local="mySessionFactory"/>
</property>
</bean> |
图4是我们已经连在一起的东西的一个概览。它展示了每个对象是怎样相关联的和怎样被Spring设置进其它对象中。把这幅图和示例应用中的Spring配置文件对比查看它们之间的关系。
图4:这是Spring怎样将在这个配置的基础上装配beans。
这个例子使用一个TransactionProxyFactoryBean,它有一个为我们已经定义了的事务管理者准备的setter方法。这是一个有用的对象,它知道怎样处理声明的事务操作和你的服务对象。你可以通过transactionAttributes属性定义事务怎样被处理,transactionAttributes属性为方法名定义模式和它们怎样参与进一个事务。
TransactionProxyFactoryBean类也有一个为一个target准备的setter,target将是一个到我们的叫作orderTarget的业务服务对象的引用。 orderTarget bean定义使用哪个业务服务对象并有一个指向setOrderDAO()的属性。orderDAO bean将居于这个属性中,orderDAO bean是我们的和持久层交流的DAO对象。
还有一个关于Spring和bean要注意的是bean能以两种模式工作。这两种模式被定义为singleton和prototype。一个bean默认的模式是singleton,意味着一个共享的bean的实例将被管理。这是用于无状态操作--像一个无状态会话bean将提供的那样。当bean由Spring提供时,prototype模式允许创建bean的新实例。你应当只有在每一个用户都需要他们自己的bean的拷贝时才使用prototype模式。
提供一个服务定位器
既然我们已经把我们的服务和我们的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;
}
} |
用户接口层配置
示例应用的用户接口层使用Struts框架。这儿我们将讨论当为一个应用分层时和Struts相关的部分。让我们从在struts-config.xml文件里检查一个Action配置开始。
<action path="/SaveNewOrder"
type="com.meagle.action.SaveOrderAction"
name="OrderForm"
scope="request"
validate="true"
input="/NewOrder.jsp">
<display-name>Save New Order</display-name>
<exception key="error.order.save"
path="/NewOrder.jsp"
scope="request"
type="com.meagle.exception.OrderException"/>
<exception key="error.order.not.enough.money"
path="/NewOrder.jsp"
scope="request"
type="com.
meagle.
exception.
OrderMinimumAmountException"/>
<forward name="success" path="/ViewOrder.jsp"/>
<forward name="failure" path="/NewOrder.jsp"/>
</action>
|
SaveNewOrder Action被用来持久化一个用户从用户接口层提交的订单。这是一个典型的Struts Action;然而,注意这个action的异常配置。这些Exceptions为我们的业务服务对象也在Spring 配置文件中配置了。当这些异常被从业务层掷出我们能在我们的用户接口里恰当的处理它们。第一个异常,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");
} |
结论
这篇文章按照技术和架构覆盖了许多话题。从中而取出的主要思想是怎样更好的给你的应用程序分层:用户接口层、持久逻辑层、和其它任何你需要的应用层。这样可以解耦你的代码,允许添加新的代码组件,使你的应用在将来更易维护。这里覆盖的技术能很好的解决这类的问题。不管怎样,使用这样的构架可以让你用其他技术代替现在的层。例如,你也许不想使用Hibernate持久化。因为你在你的DAO对象中编码到接口,你能怎样使用其它的技术或框架,比如 iBATIS,作为一个替代是显而易见的。或者你可能用不同于Struts的框架替代你的UI层。改变UI层的实现不会直接影响你的业务逻辑层或者你的持久层。替换你的持久层不会影响你的UI逻辑或业务服务层。集成一个web应用其实也不是一件烦琐的工作,靠解耦你的各应用层和用适当的框架组成它,它能变得更容易处理。
二、比较
http://qxndl.blog.sohu.com/905950.html
struts+spring+hibernate之间的关系与差别
Struts:用来作VC部分,即控制和显示作用; Spring:用来作数据库操作的事务处理,在配置文件里配置好就OK了; Hibernate:用来作DAO处理,在此用了Spring的getHibernateTemplate()方法来操作hsql进行数据增删改等操作。
Struts:用来作VC部分,即控制和显示作用;
Spring:用来作数据库操作的事务处理,在配置文件里配置好就OK了;
Hibernate:用来作DAO处理,在此用了Spring的getHibernateTemplate()方法来操作hsql进行数据增删改等操作。
1,先说说你的表示层
其实没有必要使用struts,除非你有历史遗留问题不得不用struts,因为spring的mvc已经足够好了:
a.清晰的模型对象传递,这个模型对象可以是任何java对象,如果你不在意在各层之间传递同一个对象的话,这个模型对象就可以是hibernate的persistent object,通过open session in view,你可以以一致的方式使用业务模型对象。
b.reference data,让你清晰的处理look up数据。
c. 多种可供选择的视图解析类型,可以在prpperties文件中定义page的逻辑名,或者定义在xml文件里的struts tiles逻辑名。
d.无干扰的数据绑定,一个<spring:bind>可以对模型对象和form进行绑定,就像struts自动填充formbean一样,但spring 的绑定功能不会干扰界面布局,也就是说,你仍然可以使用html编辑器对页面进行处理。
e.客户端验证。
f.服务器端验证。
g.多种可供选择的控制器,其中支持表单的控制器提供了类似vb中表单事件处理的功能,这是一系列的workflow,在你认为合适的地方,插入你的处理代码。
spring mvc与struts比较,可能只是少了很多taglib和页面布局,但这都可以通过第三方工具补充,因为视图相比于其他部分,毕竟更轻量级一些。可以选择的第三方工具可以是:displaytag,struts-menu,struts tiles,等等。
2,在说说业务逻辑部分
业务逻辑类可以用spring的beans进行配置,并由spring管理与表现层的控制器及更下层的DAO对象的关系。另外,还可以进行配置性的事务处理,一个interceptor配置,免去了你的所有烦恼。
3,dao层
用spring 封装后的hibernate API,让Hibernate继续瘦身,并且通过spring建立与上层的关系。
4,最后,说说hibernate的po
你可以选择你喜欢的任何方式进行建模,以下工具提供了足够的支持:
a. 从java对象到hbm文件:xdoclet
b. 从hbm文件到java对象:hibernate extension
c. 从数据库到hbm文件:middlegen
d. 从hbm文件到数据库:SchemaExport
至于可供参考的项目,可以看看spring的例子petclinic(spring+hibernate),还有一个不可不看的网站:http://raibledesigns.com/wiki/Wiki.jsp?page=AppFuse(struts+spring+hibernate或spring mvc + spring +hibernate)。另外,spring带的mvc step-by-step是一个很好的入门教程。
需要说明的是,spring仅仅为我们提供了一种设计和实现框架的方式,因此,项目的成功与否,是与我们的构架设计紧密相关的,在有了好的设计思想以后,善用spring,会让我们的成功来的更容易。
a
三、整合
先说说《Wiring Your Web Application with Open Source Java by Mark Eagle》这个示例,网上有这个的中英文文章。在后续的说明中我会将文章的部分内容引用进来。我使用的开发工具是 Eclipse3.1 + MyEclipse4.0M2。
针对一个简单或者复杂的 Web 应用程序,我们需要考虑诸如是怎样建立用户接口?在哪里处理业务逻辑?怎样持久化的数据?而针对这三个层次,每个层次我们都要仔细考虑:各个层该使用什么技术? 怎样的设计能松散耦合还能灵活改变? 怎样替换某个层而不影响整体构架?应用程序如何做各种级别的业务处理(比如事务处理)?等等。
对于这个示例我们采用当前流行的三种框架来做到 Web 应用程序的松散耦合:表示层我们用 Struts;业务层我们用 Spring;而持久层则用 Hibernate。
Struts 负责管理用户的请求,做出响应;提供控制器,委派调用业务逻辑;处理异常;UI 验证等。
Spring 负责处理应用程序的业务逻辑和业务校验;管理事务;提供与其它层相互作用的接口;管理业务层级别的对象的依赖等。
Hibernate 负责存储、更新、删除数据库记录等。
这篇文章举例说明如何使用这三个框架整合开发,并揭示一个请求是如何贯穿于各个层的。(从用户的加入一个Order到数据库,显示;进而更新、删除)。
1. 首先创建一组对象,这些对象有的需要持久化,有的提供业务逻辑,有的是显示接口的设计。Hibernate 允许你将数据库中的信息存放入对象,可以在连接断开的情况下把这些数据显示到UI层。而那些对象也可以返回给持续层,从而在数据库里更新。
使用 myeclipse 的 Web Project 新建一个项目 SSHTest:
创建 Order 类:
利用 eclipse 生成 Getters 和 Setters。
同样创建 OrderLineItem 类。
com.ivan.ssh.bo.Order 包含一个订单的主要信息:订单号,订单总价,订单客户名。
com.ivan.ssh.bo.OrderLineItem 包含订单的详细信息:订单单项号,单价,描述。一个订单对应于多个订单项。
Order 与 OrderLineItem 的关系是一对多的关系,这里为它们建立双向一对多的关系。
在类中我们需要分别为 Order 和 OrderLineItem 添加属性 orderLineItems(java.util.Set 类型) 和 order(Order 类型)及其 getter 和 setter 方法。
<hibernate-mapping>
<class
name="com.meagle.bo.Order"
table="Orders"
dynamic-update="false"
dynamic-insert="false">
<id
name="id"
column="Order_ID"
type="int"
unsaved-value="0">
<generator class="native">
</generator>
</id>
<set
name="orderLineItems"
table="OrderLineItem"
lazy="true"
inverse="true"
cascade="save-update"
sort="unsorted">
<key column="Order_ID"/>
<one-to-many class="com.meagle.bo.OrderLineItem"/>
</set>
<property
name="userName"
type="string"
update="true"
insert="true"
column="UserName"
not-null="true"
unique="false"/>
<property
name="total"
type="double"
update="true"
insert="true"
column="Total"
not-null="false"
unique="false"/>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class
name="com.meagle.bo.OrderLineItem"
table="OrderLineItem"
dynamic-update="false"
dynamic-insert="false">
<id
name="id"
column="OrderLineItem_ID"
type="int"
unsaved-value="0">
<generator class="native">
</generator>
</id>
<many-to-one
name="order"
class="com.meagle.bo.Order"
cascade="none"
outer-join="auto"
update="true"
insert="true"
column="Order_ID"/>
<property
name="description"
type="string"
update="true"
insert="true"
column="Description"
not-null="false"
unique="false"/>
<property
name="lineItemPrice"
type="double"
update="true"
insert="true"
column="LineItemPrice"
not-null="false"
unique="false"/>
</class>
</hibernate-mapping>
稍后我们介绍怎样配置 SessionFactory 和 Session,前者说明与哪个数据库通信,使用哪个连接池或使用了 DataSource,加载哪些持久对象,后者用来完成查找、保存、删除和更新这些操作。
3. 配置业务层,这里要创建业务服务对象(Business Service Object),用他们来执行程序的逻辑,调用持久层,得到 UI 层的 requests,处理 transactions,并且控制 exceptions。为了将这些连接起来并且易于管理,我们将使用面向方面的 SpringFramework。Spring 提供了控制倒置(Inversion of Control)和注射依赖设置(Setter Dependency Injection) 这些方式(可供选择),用 XML 文件将对象连接起来。IoC 是一个简单概念(它允许一个对象在上层接受其他对象的创建),用 IoC 这种方式让你的对象从创建中释放了出来,降低了偶合度。
我们将用一个 business service object 接收一个 DAO,用它来控制 domain objects 的持久化。 由于在这个例子中使用了 Hibernate,我们可以很方便的用其他持久框架实现同时通知 Spring 有新的 DAO 可以使用了。在面向接口的编程中,你会明白 “注射依赖”模式是怎样松散耦合你的业务逻辑和持久机制的。
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);
}
注意到这段代码里有一个 setOrderDao(),它就是一个DAO Object设置方法(注射器)。
其实现类:
public class OrderServiceSpringImpl implements IOrderService {
private static final double ORDER_MINIMUM = 100.0;
private IOrderDAO orderDAO;
public Order saveNewOrder(Order order)
throws OrderException, OrderMinimumAmountException {
// do some business logic
if (order != null && order.getTotal() == 0) {
double total = 0.0;
Set items = order.getOrderLineItems();
Iterator iter = items.iterator();
while (iter.hasNext()) {
OrderLineItem item = (OrderLineItem) iter.next();
total += item.getLineItemPrice();
}
if (total < OrderServiceSpringImpl.ORDER_MINIMUM) {
throw new OrderMinimumAmountException("Order did not exceed the order minimum");
} else {
order.setTotal(total);
}
}
Order savedOrder = null;
try {
savedOrder = getOrderDAO().saveOrder(order);
} catch (RuntimeException e) {
throw new OrderException("Could not save order " + e.toString());
}
return savedOrder;
}
public List findOrderByUser(String user) throws OrderException {
List orders = null;
try {
orders = getOrderDAO().findOrdersPlaceByUser(user);
} catch (RuntimeException e) {
// should really use a logger instead of System.out
System.out.println(
"Could not locate order by user " + e.getMessage());
throw new OrderException(
"Could not locate order by user " + e.getMessage());
}
return orders;
}
public Order findOrderById(int id) throws OrderException {
Order order = null;
try {
order = getOrderDAO().findOrderById(id);
} catch (RuntimeException e) {
// should really use a logger instead of System.out
System.out.println(
"Could not locate order by ID " + e.getMessage());
throw new OrderException(
"Could not locate order by ID " + e.getMessage());
}
return order;
}
public IOrderDAO getOrderDAO() {
return orderDAO;
}
public void setOrderDAO(IOrderDAO orderDAO) {
this.orderDAO = orderDAO;
}
}
接下去对 DAO 的实现类进行编码。既然 Spring 已经有对 Hibernate 的支持,那这个例子就直接继承 HibernateDaoSupport类了,这个类很有用,我们可以参考 HibernateTemplate(它主要是针对 HibernateDaoSupport 的一个用法,具体可以查看 Spring 的 API)。
public interface IOrderDAO {
public Order findOrderById(final int id);
public abstract List findOrdersPlaceByUser(final String placedBy);
public abstract Order saveOrder(final Order order);
}
此接口的实现类:
public class OrderHibernateDAO
extends HibernateDaoSupport
implements IOrderDAO {
public OrderHibernateDAO() {
super();
}
public Order findOrderById(final int id) {
Order order = (Order) getHibernateTemplate().load(Order.class, new Integer(id));
if(order.getOrderLineItems().size() > 0){
// collection initialized.
}
return order;
}
public List findOrdersPlaceByUser(final String placedBy) {
return getHibernateTemplate().executeFind(new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException, SQLException {
StringBuffer sb = new StringBuffer(100);
sb.append("select distinct order ");
sb.append("from Order order ");
sb.append("join order.lineItems lineItems ");
sb.append("where order.placedBy = :placedBy");
sb.append("order by order.id");
Query query = session.createQuery(sb.toString());
query.setString("placedBy", placedBy);
List list = query.list();
return list;
}
});
}
public Order saveOrder(final Order order) {
getHibernateTemplate().save(order);
return order;
}
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=448765
续前,现在我们来看看 Spring 的配置文件 applicationContext-hibernate.xml :
<beans>
<!-- 数据源定义-->
<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:3306/test</value></property>
<property name="username"><value>root</value></property>
<property name="password"><value></value></property>
</bean>
<!-- Hibernate 的 SessionFactory 定义 -->
<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="mappingResources">
<list>
<value>com/meagle/bo/Order.hbm.xml</value>
<value>com/meagle/bo/OrderLineItem.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<!-- 对单一 Hibernate SessionFactory 的事务管理器定义 -->
<bean id="myTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory"><ref local="mySessionFactory"/></property>
</bean>
<!-- 业务服务对象定义 并将它配置在事务管理器中 -->
<bean id="orderService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref local="myTransactionManager"/></property>
<property name="target"><ref local="orderTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="find*">PROPAGATION_REQUIRED,readOnly,-OrderException</prop>
<prop key="save*">PROPAGATION_REQUIRED,-OrderException,-OrderMinimumAmountException</prop>
</props>
</property>
</bean>
<!-- 业务对象实现定义 -->
<bean id="orderTarget" class="com.meagle.service.spring.OrderServiceSpringImpl">
<property name="orderDAO"><ref local="orderDAO"/></property>
</bean>
<!-- Hibernate 实现的 DAO 对象定义 -->
<bean id="orderDAO" class="com.meagle.service.dao.hibernate.OrderHibernateDAO">
<property name="sessionFactory"><ref local="mySessionFactory"/></property>
</bean>
</beans>
这里我们使用了 Spring 提供的便捷的方式-----在 Spring 内部配置中并入了 Hibernate 的配置。也可以在 Spring 外使用 Hibernate,将 Hibernate 的配置写在 hibernate.cfg.xml 中。
从下图可以看出,每个对象都联系着 Spring,并且能通过 Spring 注入到其他对象。把它与 Spring 的配置文件比较,观察他们之间的关系:
4. 既然已经将各种服务对象搭配起来了,现在需要把服务显示到其他层。 这个通常是在 Struts 或者 Swing 这层里编码。一个简单方法就是用服务定位器返回给 Spring context 。当然,可以通过直接调用 Spring 中的Bean 来做。
下面就是一个 Struts Action 中的服务定位器使用的例子(后面的 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;
}
}
既然表示层是使用 Struts 框架的,现在来看看 Struts 的配置文件是怎样的:
<struts-config>
<!-- Form Beans -->
<form-beans>
<form-bean name="OrderForm" type="com.meagle.forms.OrderForm"/>
</form-beans>
<!-- Global Exceptions -->
<global-exceptions>
<exception key="global.data.access.exception"
path="/queryOrder.jsp"
scope="request"
type="org.springframework.dao.DataAccessException"/>
</global-exceptions>
<!-- Action Mappings -->
<action-mappings>
<action path="/Index"
forward="/index.jsp"/>
<action path="/PlaceOrder"
forward="/newOrder.jsp"/>
<action path="/QueryOrder"
forward="/queryOrder.jsp"/>
<action path="/SaveNewOrder"
type="com.meagle.action.SaveOrderAction"
name="OrderForm"
scope="request"
validate="true"
input="/newOrder.jsp">
<display-name>Save New Order</display-name>
<exception key="error.order.save"
path="/newOrder.jsp"
scope="request"
type="com.meagle.exception.OrderException"/>
<exception key="error.order.not.enough.money"
path="/newOrder.jsp"
scope="request"
type="com.meagle.exception.OrderMinimumAmountException"/>
<forward name="success" path="/viewOrder.jsp"/>
<forward name="failure" path="/newOrder.jsp"/>
</action>
<action path="/FindOrderID"
type="com.meagle.action.FindOrderAction"
name="OrderForm"
scope="request"
validate="true"
input="/queryOrder.jsp">
<display-name>Find Existing Order</display-name>
<exception key="error.order.find"
path="/queryOrder.jsp"
scope="request"
type="com.meagle.exception.OrderException"/>
<forward name="success" path="/viewOrder.jsp"/>
</action>
</action-mappings>
<message-resources parameter="com.meagle.resources.ApplicationResources"/>
</struts-config>
SaveNewOrder 这个 Action 是处理表示层里提交的表单。这是 Struts 中很典型的 Action。注意观察它的 exception 配置,这些 Exceptions 也在 Spring 配置文件中配置了。 当异常在业务层被被抛出时,我们可以控制他们,并适当的显示到表示层。OrderException 在持久层保存 order 对象失败的时候被触发。这将导致事物回滚并且通过业务对象把异常回传到 Struts 这一层。OrderMinimumAmountException 也是一样。
这两个 Action 类都继承了前面提到 BaseAction,现在看看 SaveNewAction 这个类中与业务层的交互:
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;
Order order = new Order();
order.setUserName(oForm.getWhoPlacedOrder());
Set lineItems = new HashSet();
if (oForm.getItemDesc_1() != null && oForm.getItemDesc_1().length() > 0) {
OrderLineItem item = new OrderLineItem();
item.setDescription(oForm.getItemDesc_1());
item.setLineItemPrice(oForm.getItemPrice_1());
lineItems.add(item);
item.setOrder(order);
}
if (oForm.getItemDesc_2() != null && oForm.getItemDesc_2().length() > 0) {
OrderLineItem item = new OrderLineItem();
item.setDescription(oForm.getItemDesc_2());
item.setLineItemPrice(oForm.getItemPrice_2());
lineItems.add(item);
item.setOrder(order);
}
if (oForm.getItemDesc_3() != null && oForm.getItemDesc_3().length() > 0) {
OrderLineItem item = new OrderLineItem();
item.setDescription(oForm.getItemDesc_3());
item.setLineItemPrice(oForm.getItemPrice_3());
lineItems.add(item);
item.setOrder(order);
}
order.setOrderLineItems(lineItems);
getOrderService().saveNewOrder(order);
oForm.setOrder(order);
ActionMessages messages = new ActionMessages();
messages.add(Globals.MESSAGE_KEY, new ActionMessage("message.order.saved.successfully"));
saveErrors(request, messages);
return mapping.findForward("success");
}
说到这差不多了。在 web.xml 中需要引入 Spring 的配置文件。可以参考我前面的文章。
如果有需要示例的全部代码,请告诉我邮箱。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=457726
这两天从网上 down 了两个 Struts+Spring+Hibernate 的例子,一个是 Spring Live 中第二章的 myusers,一个是 Wiring your web application with open source java(这个文档网上有中文也有英文的) 中的例子,这里我取名为 SSHTest。 在 MyEclipse 中建立这两个工程并调试成功后,我看到这两个工程在组合 Struts、Spring、Hibernate 的时候有点点不同,下面就三个配置文件(web.xml、struts-config.xml、applicationContext.xml、)来看他们的不同:
myusers 的 web.xml 没有引入什么特别的。
SSHTest 的 web.xml :
通过:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
或:
<servlet>
<servlet-name>SpringContextServlet</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
Web 容器会自动加载 /WEB-INF/applicationContext.xml 初始化 ApplicationContex t实例;
也可以通过
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-*.xml</param-value>
</context-param>
使 Web 容器加载指定名称路径的 Spring 配置文件。
myusers 的 struts-config.xml
通过
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml,
/WEB-INF/action-servlet.xml"/>
</plug-in>
来加载 Spring 配置文件。
而 SSHTest 的 struts-config.xml 又没有什么特别之处了。
两个工程的 applicationContext.xml 都大同小异。
由上可以看出,即可以使用 web.xml 来使 Web 容器加载 Spring,也可以通过 struts-config.xml 来使 Web 容器加载 Spring。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=431799
posted on 2007-09-18 15:46
Ke 阅读(2584)
评论(1) 编辑 收藏 所属分类:
struts+spring+hibernate