4.9 Struts与Hibernate的整合策略
前面介绍了Hibernate的一些相关知识点,距离Hibernate进入实际开发还有一段路要走。Hibernate作为持久层解决方案,必须与其他表现层技术组合在一起才可形成一个J2EE开发框架。经常看到网上一些朋友给出的Hibernate入门示例,居然在JSP页面中访问Hibernate Configuratioin对象。甚至看到某些所谓的精通J2EE书籍,也居然在JSP页面中访问Hibernate的Configuration对象——这种现状非常让人担忧,Hibernate并不是万金油,并不是说项目中使用Hibernate就怎么了不起了,而是通过使用Hibernate,可以让J2EE应用架构更科学,可以让开发者以更好的面向对象的方式进行项目开发。
反过来说,即使不使用Hibernate,而使用普通的JDBC持久化解决方案,也不应该在JSP(表现层)访问到JDBC API(持久层API)。下面介绍如何让Hibernate和Struts进行整合,整合Spring部分将在后面章节介绍。
4.9.1 工厂模式介绍
工厂模式是指当应用程序中A组件需要B组件协助时,并不是直接创建B组件的实例,而是通过B组件的工厂——该工厂可以生成某一个类型组件的实例。在这种模式下,A组件无须与B组件以硬编码方式耦合在一起,而只需要与B组件的工厂耦合。
对于A组件而言,它只关心工厂生产的实例是否满足某种规范,即实现了某个接口(满足接口规范,即可供自己正常调用)。这种模式提供了对象之间清晰的角色划分,降低了程序的耦合。
接口产生的全部实例通常实现相同接口,接口里定义全部实例共同拥有的方法,这些方法在不同的实现类中实现方式不同。程序调用者无须关心方法的具体实现,从而降低了系统异构的代价。
下面是工厂模式的示例代码:
//Person接口定义
public interface Person
{
/**
* @param name 对name打招呼
* @return 打招呼的字符串
*/
public String sayHello(String name);
/**
* @param name 对name告别
* @return 告别的字符串
*/
public String sayGoodBye(String name);
}
该接口定义Person的规范,该接口必须拥有两个方法:能打招呼、能告别。规范要求实现该接口的类必须具有这两个方法:
//American类实现Person接口
public class American implements Person
{
/**
* @param name 对name打招呼
* @return 打招呼的字符串
*/
public String sayHello(String name)
{
return name + ",Hello";
}
/**
* @param name 对name告别
* @return 告别的字符串
*/
public String sayGoodBye(String name)
{
return name + ",Good Bye";
}
}
下面是实现Person接口的另一个实现类Chinese
public class Chinese implements Person
{
/**
* @param name 对name打招呼
* @return 打招呼的字符串
*/
public String sayHello(String name)
{
return name + ",您好";
}
/**
* @param name 对name告别
* @return 告别的字符串
*/
public String sayGoodBye(String name)
{
return name + ",下次再见";
}
}
然后看Person工厂的代码:
public class PersonFactory
{
/**
* 获得Person实例的工厂方法
* @ param ethnic 调用该实例工厂方法传入的参数
* @ return返回Person实例
*/
public Person getPerson(String ethnic)
{
//根据参数返回Person接口的实例
if (ethnic.equalsIgnoreCase("chin"))
{
return new Chinese();
}
else
{
return new American();
}
}
}
最简单的工厂模式的框架基本如上所示。
主程序部分仅仅需要与工厂耦合,而无须与具体的实现类耦合在一起。下面是主程序部分:
public class FactroyTest
{
public static void main(String[] args)
{
//创建PersonFactory的实例,获得工厂实例
PersonFactory pf = new PersonFactory();
//定义接口Person的实例,面向接口编程
Person p = null;
//使用工厂获得Person的实例
p = pf.getPerson("chin");
//下面调用Person接口的方法
System.out.println(p.sayHello("wawa"));
System.out.println(p.sayGoodBye("wawa"));
//使用工厂获得Person的另一个实例
p = pf.getPerson("ame");
//再次调用Person接口的方法
System.out.println(p.sayHello("wawa"));
System.out.println(p.sayGoodBye("wawa"));
}
}
主程序从Person接口的具体类中解耦出来,而且程序调用者无须关心Person的实例化过程,角色划分清晰。主程序仅仅与工厂服务定位结合在一起,获得工厂的引用,程序将可获得所有工厂产生的实例。具体类的变化,重要接口不发生任何改变,调用者程序代码部分几乎无须发生任何改动。
4.9.2 使用DAO模式
第1章介绍了J2EE应用的架构,最上面的表现层,表现层与MVC框架的控制器交互,控制器负责调用业务逻辑组件的业务逻辑方法来处理用户请求,而业务逻辑组件则依赖于DAO组件提供的数据库原子操作,这种模式也被称为DAO模式。
由上面关于J2EE应用架构的介绍可见,控制器总是依赖于业务逻辑组件,而业务逻辑组件总是依赖于DAO组件。也就是说,控制器需要调用业务逻辑组件的方法,而业务逻辑组件需要调用DAO组件的方法。
DAO模式的分层非常清晰,持久层访问被封装在DAO层下,而决不会扩散到业务逻辑层,更不会在JSP页面(表现层)中进行持久层访问。
注意:即使在早期的Model 1(使用JSP + JavaBean创建应用的模式,没有使用MVC设计模式)模式下,持久层访问也被封装在JavaBean中完成,而不是直接在JSP页面中进行数据库访问。对于直接在JSP中访问持久层API的做法,可以说根本不了解J2EE开发。
那么控制器采用怎样的方式访问业务逻辑组件呢?应该采用工厂模式,让控制器与业务逻辑组件的实现类分离,仅与业务逻辑工厂耦合;同样,业务逻辑组件也应该采用工厂模式访问DAO模式,而不是直接与DAO实现类耦合。
后面的案例部分会介绍更实际的整合策略,此处仅仅介绍DAO模式下两个工厂模式策略。
4.9.3 DAO组件的工厂模式
在J2EE应用开发中,可扩展性是一个随时需要关注的问题。而DAO组件是经常需要增加的项目组件,如果每次需要增加一个DAO组件都需要修改代码是相当让人沮丧的事情。为了避免这种情况,采用XML配置文件来管理所有的DAO组件,这种DAO组件配置文件的代码如下:
<?xml version="1.0" encoding="GBK"?>
<daoContext>
<!-- 配置应用需要的sonDao组件 -->
<dao id="sonDao" class="org.yeeku.dao.impl.SonDaoImpl"/>
<!-- 配置应用需要的personDao组件 -->
<dao id="personDao" class="org.yeeku.dao.impl.PersonDaoImpl"/>
</daoContext>
查看上面的配置文件可以看出,应用中有配置了两个DAO组件,因为每个DAO组件在J2EE应用中仅需要一个实例就足够了,因此DAO工厂类提供了一个缓存池来缓存每个DAO实例,并负责在应用启动时创建所有的DAO。
下面是DAO工厂类的代码:
public class DaoFactory
{
//用于缓存DAO实例的Map对象
private Map<String, Dao> daoMap = new HashMap<String , Dao>();
//将DAO工厂写成单态模式
private static DaoFactory df;
//DAO工厂的构造器
private DaoFactory()throws Exception
{
//使用SAXReader来负责解析daoContext.xml配置文档
Document doc = new SAXReader().read(new File(ConstantsUtil.realPath
+ "\\daoContext.xml"));
//获取文档的根文档
Element root = doc.getRootElement();
//获取daoContext根元素的所有子元素
List el = root.elements();
for (Iterator it = el.iterator();it.hasNext() ; )
{
//每个子元素对应一个DAO组件
Element em = (Element)it.next();
String id = em.attributeValue("id");
//获取实现类
String impl = em.attributeValue("class");
//通过反射,根据类名创建DAO组件的实例
Class implClazz = Class.forName(impl);
Dao d = (Dao)implClazz.newInstance();
//将创建的DAO组件放入缓存池中
daoMap.put(id, d);
}
}
//单态模式必须提供一个入口方法来创建DAO工厂的方法
public static DaoFactory instance()throws Exception
{
//如果DAO工厂还未创建
if (df == null)
{
df = new DaoFactory();
}
return df;
}
//下面的方法用于根据DAO组件ID获取DAO组件
public Dao getDao(String id)
{
return daoMap.get(id);
}
}
通过上面的工厂类代码可以看出,DAO工厂负责初始化所有的DAO组件。系统每增加一个DAO组件,无须再修改任何代码,仅仅需要在daoContext.xml配置文件中增加配置即可。
注意:这种整合策略非常优秀。可扩展性很好,如果应用需要增加一个DAO组件,只需要修改配置文件,并提供相应的DAO组件实现即可。而且,如果有一天需要重构DAO组件,只须提供修改过的DAO组件实现类,而业务逻辑组件无须任何改变。
业务逻辑组件代码无须与DAO实现类耦合,业务逻辑组件的代码面向DAO组件的接口编程,将业务逻辑组件和DAO组件的耦合降低到接口层次。
4.9.4 业务逻辑组件的工厂模式
与此类似的是,业务逻辑组件完全可以采用这种编程模式,业务逻辑组件的配置文件代码如下:
<?xml version="1.0" encoding="GBK"?>
<appContext>
<!-- 配置应用需要的业务逻辑组件,每个业务逻辑组件对应一个app元素 -->
<app id="wawa" class="org.yeeku.service.impl.WawaServiceImpl"/>
</appContext>
业务逻辑组件工厂同样可根据该配置文件来初始化所有业务逻辑组件,并将业务逻辑组件放入缓存池中,让控制器与业务逻辑组件的耦合降低到接口层次。业务逻辑组件的工厂类代码如下:
public class AppFactory
{
private Map<String , Object> appMap = new HashMap<String , Object>();
//业务逻辑组件工厂采用单态模式
private static AppFactory df;
//业务逻辑组件工厂的私有构造器
private AppFactory()throws Exception
{
//使用SAXReader来负责解析appContext.xml配置文档
Document doc = new SAXReader().read(new File(ConstantsUtil.realPath
+ "\\appContext.xml"));
//获取文档的根文档
Element root = doc.getRootElement();
//获取appContext根元素的所有子元素
List el = root.elements();
for (Iterator it = el.iterator();it.hasNext() ; )
{
//每个app元素对应一个业务逻辑组件
Element em = (Element)it.next();
String id = em.attributeValue("id");
//根据配置文件指定的业务逻辑组件实现类来创建业务逻辑组件实例
String impl = em.attributeValue("class");
Class implClazz = Class.forName(impl);
Object d = implClazz.newInstance();
//将业务逻辑组件放入缓存池中
appMap.put(id , d);
}
}
//单态模式必须提供入口方法,用于创建业务逻辑组件工厂
public static AppFactory instance()throws Exception
{
//如果业务逻辑组件工厂为空
if (df == null)
{
df = new AppFactory();
}
return df;
}
//根据业务逻辑组件的id属性获取业务逻辑组件
public Object getApp(String id)
{
//直接从缓存池中取出业务逻辑组件,并返回
return appMap.get(id);
}
}
从某种程度上来讲,这种方式与后来Spring的控制反转(Inversion of Control,IoC)容器有异曲同工之妙,但Spring的IoC容器则提供了更多的功能。
上面的两个类中都用到了一个ConstantsUtil,它仅用于保存一个全局变量,有一个public static的realPath属性,该属性用于保存应用在服务器中的路径。
posted on 2009-07-19 10:08
jadmin 阅读(64)
评论(0) 编辑 收藏