Chinaunix首页 | 论坛 | 博客
  • 博客访问: 30468154
  • 博文数量: 708
  • 博客积分: 12163
  • 博客等级: 上将
  • 技术积分: 8240
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-04 20:59
文章分类

全部博文(708)

分类: Java

2009-06-05 11:53:25

在最近的Web应用开发中,Hibernate,Spring,Struts框架做为开源的轻量级框架,正被越来越多的开发者使用,而如何将这些框架集成起来,应用到WebSphere Portlet开发中去,将是本文讨论的内容。本文还描述了将这些框架应用到Portlet上的时候,遇到的一些细节问题。

Hibernate是最近比较流行的一个用来处理O/R Mapping的持久层框架。它的工作原理是通过文件把值对象和数据库表之间建立起一个映射关系,这样,我们只需要通过操作这些值对象和Hibernate提供的一些基本类,就可以达到使用数据库的目的。使用Hibernate可以很好的将持久层和逻辑层进行隔离。请参阅参考资料一节获得更多Hibernate框架的信息。

Spring框架是一个包含了MVC层,中间层和持久层管理的框架,其核心模块是bean管理,现在很多的应用都采用Spring的bean管理机制来管理其逻辑层。请参阅参考资料一节获得更多Spring框架的信息。

Struts框架是Apache Jakarta项目的一部分,它为构建Web应用程序提供了很流行的MVC框架。WebSphere Portal V5提供了Struts Portlet框架,这个框架针对Portlet应用,将Struts的类包和taglib在URL生成,URL解析等处,做了自己的改写,使Portlet框架也可以支持Struts应用,将其作为Portlet来部署。

本文将通过构建一个使用Hibernate,Spring,Struts框架的Portlet应用,来描述如何在Portlet应用中使用这些框架。对于那些并不熟悉使用这些框架进行开发的Web应用程序的读者来说,本文提供了足够的信息使您可以掌握一些基础知识。但本文并不是一篇介绍如何使用这些框架的教程。

在本文中讨论的应用程序的开发或部署中用到了以下产品:
WebSphere Portal 5.0.2.2
WebSphere Studio Application Developer 5.1.2

请您注意!如果您的部署服务器Portal Server的版本低于5.0.2.2,您在部署web.xml的时候,在过滤器设置上将会遇到问题导致不能部署,从而无法通过设置过滤器来解决应用程序的中文问题。如果您的开发环境低于WebSphere Studio Application Developer 5.1.2,您可能不能得到本文中所述的Struts Portlet的全部支持。





回页首


我们的示例应用程序将实现对一组持久数据的标准的创建、读取、更新、删除(Create/Read/Update/Delete,CRUD)操作。这个示例应用程序为一个新闻编辑程序,用户可以在列表中查看新闻,并新建,修改,删除新闻。

虽然这个示例应用程序是一个比较简单的应用,但为了更好的阐述Hibernate,Spring和Struts的作用范围,我们还是将这个应用程序进行分层的阐述:

和通常大多数的Web应用程序一样,本应用程序分为四层,这四层是:presentation(描述),business(业务),persistence(持久)和domain model(域模型)。

一般来讲,一个典型的Web应用的的末端应该是表示层。用来管理用户的请求,做出相应的响应,给出显示。在这里,我们使用了Struts Portlet框架来实现本应用程序的表示层。

域模块层由实际需求中的业务对象组成,即我们常说的BO(Business Object) 比如, Order , Pet等等。 开发者在这层 不用管那些DTOs,仅关注domain object即可。 例如,Hibernate允许你将数据库中的信息存放入对象(domain objects),这样你可以在连接断开的情况下把这些数据显示到UI层。 而那些对象也可以返回给持久层,从而在数据库里更新。

一个典型Web应用的中间部分是业务层或者服务层。这一层最容易受到忽视,从而导致大量的代码紧密的耦合在一起,从而使整个程序变的难以维护。在这里,我们使用Spring框架来解决这个问题,Spring把程序中所涉及到包含业务逻辑和Dao?的Objects--例如transaction management handler(事物管理控制)、Object Factories(对象工厂)、service objects(服务组件)--都通过XML来配置联系起来,从而使业务层变得非常灵活和易于维护。

持久层是我们典型的Web应用的另一个末端。现在已经有很多很好的ORM开源框架来解决持久层的各种问题,尤其是Hibernate。 Hibernate为Java提供了OR持久化机制和查询服务, 它还给已经熟悉SQL和JDBC API 的Java开发者一个学习桥梁,他们学习起来很方便。 Hibernate的持久对象是基于POJO和Java collections。

1. 打开WSAD,点击 文件-新建-其他,在弹出的对话框左边选择Portlet开发,右边选择Portlet项目。如图1:



图1

2. 点下一步,在项目名中输入Sample,类型选择Struts Portlet,如果没有需要配置的高级选项,点击完成。这样,就创建了一个设置为使用 WebSphere Portal Server 所包括的 Struts Portlet Framework 的 Portlet。如图2:



图2

3. 建立目录结构。在刚建的Sample项目上点右键,选择属性-JAVA构建路径-源,选中Sample/JavaSource,选中'除去',将这个目录从构建路径中清除。点击'添加文件夹',在JavaSource下创建目录:dao,service,web,并将这三个目录添加到构建路径中。在下面的步骤中,我们将在dao目录下放置和持久层相关的代码,在service目录下放置业务层相关的代码,在web目录下放置struts相关的action,form代码。在Sample目录下建立Test目录,Test目录下建立dao,service目录。我们将在Test/dao下放置dao层的JUnit测试代码,在Test/service下放置service层的JUnit测试代码。如图3:



图3

4. 配置环境变量将附件中lib目录下的文件,全部拷贝到Sample项目的对象lib目录下,并在构建路径中完成类路径设置。

5. 配置数据库。在附件中,你可以找到两个文件,分别名为build.xml和build.properties,将其拷贝到Sample应用的根目录下。修改build.properties文件中关于数据库的设置,包括数据库用户名,数据库密码,数据库名,数据库驱动类名,连接数据库的URL,以及Hibernate需要使用的数据库Dialect类名。将其设置为你的测试环境数据库一致即可。默认的数据库设置为MySql。





回页首


这层是编码的着手点,我们的编码就从这层开始。 本应用中的Domain Object比较简单,只使用了一个对象:com.ibm.sample.bo.InfoObject.java。

代码清单 :
package com.ibm.sample.bo;
public class InfoObject {
	private Long infoId;
	private String title;
	private String content;
}

infoId记录了这个InfoObject对象的唯一标识,title记录了新闻标题,content记录了新闻内容。

1. 持久化BO。 Hibernate是通过POJO工作的, 因此我们先给InfoObject对象的fileds 加上getter,setter方法。Hibernate通过XML文件来映射(OR)对象,在这里,我们使用XDoclet工具来生成hibernate的XML映射文件。为了使用Xdoclet这个工具,我们需要在BO对象的代码里面添加一些描述语句。修改后的BO对象代码如下:



package com.ibm.sample.bo;
/**
 * @author rayguo  mail:guorui@cn.ibm.com
 * 
 * @hibernate.class table="InfoObject"
 * 
 */
public class InfoObject {
	private Long infoId;
	private String title;
	private String content;
	/**
	 * Returns the Content.
	 * @return String
	 *
	 * @hibernate.property
	 * @hibernate.column name="Content" not-null="false"
	 *  length="3000"
	 */
	public String getContent() {
		return content;
	}
	/**
	 * Returns the id.
	 * @return Long
	 *
	 * @hibernate.id column="infoId"
	 *  generator-class="native"
	 */
	public Long getInfoId() {
		return infoId;
	}
	/**
	 * Returns the Title.
	 * @return String
	 *
	 * @hibernate.property
	 * @hibernate.column name="title" not-null="true"
	 *  length="200"
	 */
	public String getTitle() {
		return title;
	}
	public void setContent(String string) {
		content = string;
	}
	public void setInfoId(Long long1) {
		infoId = long1;
	}
	public void setTitle(String string) {
		title = string;
	}
}

在类名前的注释@hibernate.class table="InfoObject",表明了这个类将被映射到数据库表InfoObject,在get方法前的注释,表明了每个属性在数据库表中的对应字段。

2.运行Ant的XDoclet任务,生成InfoObject.hbm.xml文件。在WSAD中右键点击build.xml文件,并选择"运行Ant",运行其中的hibernatedoclet任务,将会在classes目录下生成所需要的InfoObject.hbm.xml文件。在这个文件中,还定义了用来生成数据库表结构的任务,将在下面做详细说明。

3.创建DAO接口。为了程序的扩展性,我们首先需要创建一个提供数据访问服务的接口层,定义出对外的访问接口,在本示例中,为IInfoObjectDAO,代码如下:



package com.ibm.sample.dao;
public interface IInfoObjectDAO {
	public abstract InfoObject saveInfoObject(InfoObject info);
	
	public abstract InfoObject getInfoObjectById(Long infoId);
	
	public abstract List getAllInfoObjects();
	
	public abstract void removeInfoObject(Long infoId);
}

这个接口定义了对InfoObject的RUCD各项操作。

4. 创建DAO层的实现。本示例的DAO层实现,我们采用了Hibernate,按通常的实现,我们需要先得到Hibernate的session对象,然后调用session对象的save,delete,update等方法来实现对数据对象的CRUD操作,但由于Spring框架已经提供了对Hibernate框架的良好支持,使我们不再需要再头痛于Hibernate的session管理,事务管理等方面,这些Spring框架已经进行了很好的封装,我们只需要将我们的Hibernate实现类继承HibernateDaoSupport类,然后通过调用HibernateTemplate类上的方法,就可以实现我们需要的数据对象访问的操作。代码如下:



package com.ibm.sample.dao.hibernate;
public class InfoObjectDAOHibernate extends HibernateDaoSupport implements IInfoObjectDAO {
	public InfoObjectDAOHibernate(){
		super();
	}
	public InfoObject saveInfoObject(InfoObject info) {
		
		getHibernateTemplate().saveOrUpdate(info);
		
		return info;
	}
	
	public InfoObject getInfoObjectById(Long infoId){
		
		InfoObject info =
					(InfoObject) getHibernateTemplate().load(InfoObject.class, infoId);
		return info;
	}
	public void removeInfoObject(Long infoId) {
		
		InfoObject info = getInfoObjectById(infoId);
		getHibernateTemplate().delete(info);
	}
	public List getAllInfoObjects() {
		return getHibernateTemplate().loadAll(InfoObject.class);
	}
}

5. 通过配置,将Spring框架与Hibernate框架结合使用。
如果你以前使用过Hibernate,你现在该感到有些迷惑:使用Hibernate框架的时候,需要提供的hibernate.cfg.xml配置文件应该放在哪里呢?为了使Spring框架能够真正的感知到Hibernate对象,为其添加事务管理,SessionFactory管理等功能,我们需要添加一个Spring的配置文件,而且,Spring提供了一个便捷的方式-----在Spring内部配置中并入了Hibernate的hibernate.cfg.xml配置文件。首先在Sample项目的JavaSource/dao目录下,建立文件applicationContext-hibernate.xml,在文件中添加如下内容:





	
	  
	
		
			com.mysql.jdbc.Driver
		
		
			jdbc:mysql://localhost:3306/infos
		
		
			root
		
		
			
		
		
	
	
	
		
			
		
		
			
				com/ibm/sample/bo/InfoObject.hbm.xml	
			
				
		
		
			
				net.sf.hibernate.dialect.MySQLDialect
			
				
	
	
	
		
		
	


Spring的核心功能就是Bean管理,在这个配置文件中,我们配置了3个Java对象,id分别为:dataSource, mySessionFactory和infoObjectDAO。mySessionFactory的dataSource属性引用了dataSource对象,infoObjectDAO的sessionFactory属性又引用了mySessionFactory对象。在Spring框架启动的时候,会自动的根据这个配置文件,生成相应的对象,并将生成的对象注入到对应的属性中去,这就是所谓的"依赖注入"(dependency injection)。通过这样的方式,可以将我们从 单例模式(singleton objects)和工厂模式(factories)中解放出来,降低代码的维护成本。在这里,mySessionFactory中配置的属性,对应于Hibernate的hibernate.cfg.xml配置文件。

dataSource中的属性设置是针对MySql数据库的,你需要将其改成与你的测试环境数据库一致。

好了,至此,我们完成了示例应用的DAO层创建工作,是不是感到有些心神不宁?是的,虽然我们用的不是测试驱动开发(Test Driver Development),可现在也该写点测试用例来测试一下我们刚刚新建的DAO层了。

1. 测试前的数据准备工作。

如果你使用的是MySql数据库,运行ant任务setup-db,此任务将创建一个名为infos的数据库,并在数据库中建立数据表infoobject。如果你使用的不是MySql数据库,你需要手工建立测试数据库,然后运行ant任务db-prepare,将会在数据库中自动建立表infoobject。

2. 编写单元测试基类



package com.ibm.sample.dao;
public class BaseDAOTestCase extends TestCase{
	protected final static XmlBeanFactory factory ;
	static {
		Resource rs1 = new ClassPathResource("applicationContext-hibernate.xml");
		factory = new XmlBeanFactory(rs1);
	}
}

这个基类非常简单,完成的功能就是通过读入Spring的配置文件,构建一个Spring的bean管理工厂,通过将这段代码包含在静态段中,可以确保Spring的Bean工厂对所有的测试只装载了一次。

3. 编写DAO的单元测试

本示例中用到的对InfoObjectDAO进行测试的类为InfoObjectDAOTest,具体代码可以查看附件中的InfoObjectDAOTest.java类代码。测试覆盖了InfoObject的各个方法,你可以简单的将其拷贝到自己的项目中并运行,如果数据库都配置正确的话,你将看到绿色的状态条,OK,测试通过,我们的DAO层已经可以顺利运行了,下面我们进入业务层代码的编写。

现在,我们需要来构建我们的BSO(business service objects)了,用来执行程序的逻辑,调用持久层,得到UI层的requests,处理transactions,并且控制exceptions。 在这里,我们将使用Spring框架,很快,你就会感受到使用Spring框架来管理业务层,将给你的应用程序带来极大的灵活性,和更松散的耦合度。

1. 建立业务服务对象接口

首先我们需要做的,还是要定义出我们在业务层提供的接口。在Spring框架中,任何注册到Spring框架中的bean,如果实现了某个接口,那么在得到这个bean的时候,只能将其下溯造型成其接口进行操作,而不能直接下溯造型成具体的类型进行操作。原因在于Spring的AOP实现机制, Spring中的Bean管理实际上是基于动态AOP机制实现,为了实现动态AOP,Spring在默认情况下会使用Java Dynamic Proxy,但是,Dynamic Proxy要求其代理的对象必须实现一个接口,该接口定义了准备进行代理的方法。而对于没有实现任何接口的Java Class,需要采用其他方式,Spring通过CGLib实现这一功能。当类实现了一个接口之后,Spring将通过Java Dynamic Proxy机制实现代理功能,此时返回的Bean,是通过java.lang.reflect.Proxy.newProxyInstance方法创建的其接口的一个代理实现,这个实例实现了其接口,但与类已经没有继承关系,因此无法通过下溯造型进行强制转型,如果进行转换,则会抛出异常。这也就强制要求编程人员要面向接口编程,使程序员能够从接口的角度考虑程序设计,从而降低了程序的耦合度。



package com.ibm.sample.service;
public interface IInfoObjectService {
	public abstract InfoObject saveInfoObject(InfoObject infoObject) throws InfoObjectException;
	public abstract InfoObject findInfoObjectById(Long id) throws InfoObjectException;
	
	public abstract List findAllInfoObjects() throws InfoObjectException;
	
	public abstract void removeInfoObject(Long deleteId) throws InfoObjectException;
	public abstract void setInfoObjectDAO(IInfoObjectDAO infoObjectDAO);
}

通过接口可以看到,在业务层对底层的Exception进行了捕捉,并进行了统一的封装,再用定义好的业务服务级别的Exception抛出。注意到这段代码里有一个 setInfoObjectDAO(),它就是一个DAO Object设置方法,将DAO的实现注射到Service对象中。 但这里并没有一个getInfoObjectDao的方法,这不必要,因为并不会在外部访问这个DAO。Service层将调用这个DAO Object和持久层通信。我们将用Spring把DAO Object 和 business service object搭配起来的。因为我们是面向接口编程的,所以并不需要将实现类紧密的耦合在一起。

2. 实现业务服务对象接口,并通过Spring将其和DAO对象关联起来因为本例比较简单,所以接口的实现也很简单,并没有什么复杂的操作,通过调用InfoObjectDAO对象上的方法,就可以实现服务对象接口,具体的代码见附件中的com.ibm.sample.service.impl. InfoObjectServiceImpl.java文件。我们主要需要关注的是如何通过Spring将业务对象与DAO对象关联起来,并实现事务。在Spring的配置文件中添加如下代码:



		
	
		
	
	
		
		
		
		
			
				PROPAGATION_REQUIRED,readOnly,-InfoObjectException
				PROPAGATION_REQUIRED,-InfoObjectException
			
		
	
	
	
		
	
	

在这里, myTransactionManager引用了mySessionFactory bean。 本例使用一个TransactionProxyFactoryBean,它定义了一个属性transactionManager。 这个对象很有用,它能很方便的处理你申明的事物还有Service Object。你可以通过transactionAttributes 属性来定义怎样处理。TransactionProxyFactoryBean 还有个属性target. 这将会注入我们的 Business service object(infoObjectTarget)引用, infoObjectTarget定义了 业务服务层,并且它还有个属性,将会注入我们的DAO对象(InfoObjectDAO)引用,通过这个配置,我们就将DAO对象和Service Object对象关联了起来,并在Business Service这一层提供了事务管理,在InfoObjectService中所有以find开头的方法,则以只读的事务处理机制进行处理。(设为只读型事务,可以使持久层尝试对数据操作进行优化,如对于只读事务Hibernate将不执行flush操作,而某些数据库连接池和JDBC 驱动也对只读型操作进行了特别优化。);而使用save开头的方法,将会纳入事务管理范围。如果此方法中抛出异常,则Spring将当前事务回滚,如果方法正常结束,则提交事务。

在这里,我们的DAO层是使用Hibernate实现的,如果我们将DAO层的实现技术改为JDBC,JDO,DAO等,只需要实现IInfoObjectDAO接口,并在Spring配置文件里,将infoObjectDAO bean的实现类名替换为新实现的类名即可,如此就可以将改动控制在最小的范围之内,不会因为DAO层的变化而引起程序结构大规模的改变,显得非常的灵活,具有良好的可维护性。

我们已经建立了应用程序的DAO层和Service层,现在我们需要做的就是将Service的接口暴露给表示层,使表示层能够调用到Service层的接口,并将处理结果展现给用户。在这一层,我们在本示例中将使用Struts Portlet框架编写Portlet进行展现。

设计页面流程

我们先设计一下实现本示例功能的页面操作流程:

用户访问本示例Portlet后,首先看到的是新闻列表,点击新闻链接,可以查看新闻内容,点击新建按钮,进入新建新闻页面,可以新建新闻,选择新闻后,点击编辑新闻按钮,进入编辑新闻页面,可以编辑新闻,选择新闻后,点击删除按钮,可以删除新闻。

由此,我们可以设计出Struts框架下应用需要的元素:

FormBean 表单bean
InfoObjectForm 我们在com.ibm.sample.web.forms包下建立InfoObjectForm类,用来记录一条新闻信息的Formbean对象
Action 操作
我们在 com.ibm.sample.web.actions 包中创建三个 Struts 操作。
GetInfoObjectAction
SaveInfoObjectAction
ListInfoObjectsAction
JSP
ListInfoObjects.jsp :展示新闻列表页面
ViewInfoObject.jsp : 查看新闻页面
EditInfoObject.jsp : 编辑新闻页面

整个应用的Web图如下:



图4

1. 配置web.xml文件

为了实现表现层对Service层的调用,我们首先需要更改web.xml文件,在里面添加如下代码:



		
		contextConfigLocation
		/WEB-INF/classes/applicationContext-hibernate.xml
	
	
	  SpringContextServlet
	  org.springframework.web.context.ContextLoaderServlet
	  1
		
	
		SpringContextServlet
		/*
	
	

通过这样的配置,示例应用程序在启动的时候,会首先初始化Spring框架自带的ContextLoaderServlet,这个Servlet的作用就是读取由contextConfigLocation指定的Spring配置文件的位置,初始化Spring框架的Context对象,并将这个对象保存在ServletContext中,留待Action调用。

同时,为了解决中文输入问题,我们在web.xml中加入过滤器,过滤器的具体代码请见附件。



		
		SetCharacterEncodingFilter
		SetCharacterEncodingFilter	
		com.ibm.sample.web.filter.SetCharacterEncodingFilter
		
			encoding
			GB2312
		
		
			ignore
			true
		
	
	
		SetCharacterEncodingFilter
		/SetCharacterEncodingFilter
	
	
		SetCharacterEncodingFilter
		action
	
	

2.定义BaseAction



public class BaseAction  extends StrutsAction{
	protected transient final Log log = LogFactory.getLog(getClass());
	private static WebApplicationContext wac = null;
	
	public Object getBean(String name) {
		return wac.getBean(name);
	}
	
	public void setServlet(ActionServlet actionServlet) {
		super.setServlet(actionServlet);
		ServletContext servletContext = actionServlet.getServletContext();
		wac =
			WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
	}
}

可以看到,在BaseAction中,维持了一个WebApplicationContext对象,通过调用这个对象的getBean方法,传入配置文件中的bean id,我们就可以定位到这个bean,也就是我们前面定义的业务层的各种服务。

在这里,我们的Action继承自StrutsAction,这个类继承自Struts框架中的Action类,其作用在Portlet Struts框架中与Action类在Struts框架中的作用类似。我们需要通过重载其public ActionForward execute(ActionMapping mapping,ActionForm form,PortletRequest request)throws Exception方法来实现我们自己的Action类。与Struts框架中的Action类相比,可以发现这个方法的参数中取消了Response对象,使我们无法简单的引用到,这个设计是由于IBM的WebSphere Portal Server的Portlet框架设计引起的,Portlet 处理分两阶段实现,操作阶段和呈现阶段。操作处理在呈现显示视图之前执行。在操作阶段,只有请求对象才会被传递给 portlet,而响应对象则不会传递,一些在操作阶段提供的信息(即请求参数)在呈现阶段不再可用。另外,因为在 portlet 没有新的事件发生时,刷新 portlet 页面时会调用呈现方法(如 doView()),因此所有呈现该页面所需的信息必须在每次调用该方法时可用,这就意味着,所有需要呈现的信息,都需要保存在PortletSession对象中,而不能保存在PortletRequest对象中,如果保存在Request对象中,在刷新页面的时候,会因为没有可用的变量而导致页面出错。

Action类的具体实现,请参见 附件中的代码文件

整个项目文件结构图:







回页首


本示例介绍了如何在 Portlet 开发中引入 Hibernate,Spring 和 Struts Portlet 框架,在下面的下载部分提供了本例的完整实现。 附件是本示例的war包,其中已经包含了示例的源代码,可以直接将其导入WSAD中查看。

阅读(1579) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~