4.9 Struts與Hibernate的整合策略
前面介紹了Hibernate的一些相關(guān)知識點(diǎn),距離Hibernate進(jìn)入實(shí)際開發(fā)還有一段路要走。Hibernate作為持久層解決方案,必須與其他表現(xiàn)層技術(shù)組合在一起才可形成一個J2EE開發(fā)框架。經(jīng)常看到網(wǎng)上一些朋友給出的Hibernate入門示例,居然在JSP頁面中訪問Hibernate Configuratioin對象。甚至看到某些所謂的精通J2EE書籍,也居然在JSP頁面中訪問Hibernate的Configuration對象——這種現(xiàn)狀非常讓人擔(dān)憂,Hibernate并不是萬金油,并不是說項(xiàng)目中使用Hibernate就怎么了不起了,而是通過使用Hibernate,可以讓J2EE應(yīng)用架構(gòu)更科學(xué),可以讓開發(fā)者以更好的面向?qū)ο蟮姆绞竭M(jìn)行項(xiàng)目開發(fā)。
反過來說,即使不使用Hibernate,而使用普通的JDBC持久化解決方案,也不應(yīng)該在JSP(表現(xiàn)層)訪問到JDBC API(持久層API)。下面介紹如何讓Hibernate和Struts進(jìn)行整合,整合Spring部分將在后面章節(jié)介紹。
4.9.1 工廠模式介紹
工廠模式是指當(dāng)應(yīng)用程序中A組件需要B組件協(xié)助時,并不是直接創(chuàng)建B組件的實(shí)例,而是通過B組件的工廠——該工廠可以生成某一個類型組件的實(shí)例。在這種模式下,A組件無須與B組件以硬編碼方式耦合在一起,而只需要與B組件的工廠耦合。
對于A組件而言,它只關(guān)心工廠生產(chǎn)的實(shí)例是否滿足某種規(guī)范,即實(shí)現(xiàn)了某個接口(滿足接口規(guī)范,即可供自己正常調(diào)用)。這種模式提供了對象之間清晰的角色劃分,降低了程序的耦合。
接口產(chǎn)生的全部實(shí)例通常實(shí)現(xiàn)相同接口,接口里定義全部實(shí)例共同擁有的方法,這些方法在不同的實(shí)現(xiàn)類中實(shí)現(xiàn)方式不同。程序調(diào)用者無須關(guān)心方法的具體實(shí)現(xiàn),從而降低了系統(tǒng)異構(gòu)的代價。
下面是工廠模式的示例代碼:
//Person接口定義
public interface Person
{??
??? /**
??? * @param name 對name打招呼
??? * @return 打招呼的字符串
??? */
??? public String sayHello(String name);
??? /**
??? * @param name 對name告別
??? * @return 告別的字符串
??? */
??? public String sayGoodBye(String name);
}
該接口定義Person的規(guī)范,該接口必須擁有兩個方法:能打招呼、能告別。規(guī)范要求實(shí)現(xiàn)該接口的類必須具有這兩個方法:
//American類實(shí)現(xiàn)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";
??? }
}
下面是實(shí)現(xiàn)Person接口的另一個實(shí)現(xiàn)類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實(shí)例的工廠方法
??? * @ param ethnic 調(diào)用該實(shí)例工廠方法傳入的參數(shù)
??? * @ return返回Person實(shí)例
??? */
??? public Person getPerson(String ethnic)
??? {
??????? //根據(jù)參數(shù)返回Person接口的實(shí)例
??????? if (ethnic.equalsIgnoreCase("chin"))
??????? {
??????????? return new Chinese();
??????? }
??????? else
??????? {
??????????? return new American();
??????? }
??? }
}
最簡單的工廠模式的框架基本如上所示。
主程序部分僅僅需要與工廠耦合,而無須與具體的實(shí)現(xiàn)類耦合在一起。下面是主程序部分:
public class FactroyTest
{
??? public static void main(String[] args)
??? {
??????? //創(chuàng)建PersonFactory的實(shí)例,獲得工廠實(shí)例
??????? PersonFactory pf = new PersonFactory();
??????? //定義接口Person的實(shí)例,面向接口編程
??????? Person p = null;
??????? //使用工廠獲得Person的實(shí)例
??????? p = pf.getPerson("chin");
??????? //下面調(diào)用Person接口的方法
??????? System.out.println(p.sayHello("wawa"));
??????? System.out.println(p.sayGoodBye("wawa"));
??????? //使用工廠獲得Person的另一個實(shí)例
??????? p = pf.getPerson("ame");
??????? //再次調(diào)用Person接口的方法
??????? System.out.println(p.sayHello("wawa"));
??????? System.out.println(p.sayGoodBye("wawa"));
??? }
}
主程序從Person接口的具體類中解耦出來,而且程序調(diào)用者無須關(guān)心Person的實(shí)例化過程,角色劃分清晰。主程序僅僅與工廠服務(wù)定位結(jié)合在一起,獲得工廠的引用,程序?qū)⒖色@得所有工廠產(chǎn)生的實(shí)例。具體類的變化,重要接口不發(fā)生任何改變,調(diào)用者程序代碼部分幾乎無須發(fā)生任何改動。
4.9.2 使用DAO模式
第1章介紹了J2EE應(yīng)用的架構(gòu),最上面的表現(xiàn)層,表現(xiàn)層與MVC框架的控制器交互,控制器負(fù)責(zé)調(diào)用業(yè)務(wù)邏輯組件的業(yè)務(wù)邏輯方法來處理用戶請求,而業(yè)務(wù)邏輯組件則依賴于DAO組件提供的數(shù)據(jù)庫原子操作,這種模式也被稱為DAO模式。
由上面關(guān)于J2EE應(yīng)用架構(gòu)的介紹可見,控制器總是依賴于業(yè)務(wù)邏輯組件,而業(yè)務(wù)邏輯組件總是依賴于DAO組件。也就是說,控制器需要調(diào)用業(yè)務(wù)邏輯組件的方法,而業(yè)務(wù)邏輯組件需要調(diào)用DAO組件的方法。
DAO模式的分層非常清晰,持久層訪問被封裝在DAO層下,而決不會擴(kuò)散到業(yè)務(wù)邏輯層,更不會在JSP頁面(表現(xiàn)層)中進(jìn)行持久層訪問。
注意:即使在早期的Model 1(使用JSP + JavaBean創(chuàng)建應(yīng)用的模式,沒有使用MVC設(shè)計模式)模式下,持久層訪問也被封裝在JavaBean中完成,而不是直接在JSP頁面中進(jìn)行數(shù)據(jù)庫訪問。對于直接在JSP中訪問持久層API的做法,可以說根本不了解J2EE開發(fā)。
那么控制器采用怎樣的方式訪問業(yè)務(wù)邏輯組件呢?應(yīng)該采用工廠模式,讓控制器與業(yè)務(wù)邏輯組件的實(shí)現(xiàn)類分離,僅與業(yè)務(wù)邏輯工廠耦合;同樣,業(yè)務(wù)邏輯組件也應(yīng)該采用工廠模式訪問DAO模式,而不是直接與DAO實(shí)現(xiàn)類耦合。
后面的案例部分會介紹更實(shí)際的整合策略,此處僅僅介紹DAO模式下兩個工廠模式策略。
4.9.3 DAO組件的工廠模式
在J2EE應(yīng)用開發(fā)中,可擴(kuò)展性是一個隨時需要關(guān)注的問題。而DAO組件是經(jīng)常需要增加的項(xiàng)目組件,如果每次需要增加一個DAO組件都需要修改代碼是相當(dāng)讓人沮喪的事情。為了避免這種情況,采用XML配置文件來管理所有的DAO組件,這種DAO組件配置文件的代碼如下:
<?xml version="1.0" encoding="GBK"?>
<daoContext>
??? <!-- 配置應(yīng)用需要的sonDao組件 -->
??? <dao id="sonDao" class="org.yeeku.dao.impl.SonDaoImpl"/>
??? <!-- 配置應(yīng)用需要的personDao組件 -->
??? <dao id="personDao" class="org.yeeku.dao.impl.PersonDaoImpl"/>
</daoContext>
查看上面的配置文件可以看出,應(yīng)用中有配置了兩個DAO組件,因?yàn)槊總€DAO組件在J2EE應(yīng)用中僅需要一個實(shí)例就足夠了,因此DAO工廠類提供了一個緩存池來緩存每個DAO實(shí)例,并負(fù)責(zé)在應(yīng)用啟動時創(chuàng)建所有的DAO。
下面是DAO工廠類的代碼:
public class DaoFactory
{
??? //用于緩存DAO實(shí)例的Map對象
??? private Map<String, Dao> daoMap = new HashMap<String , Dao>();
??? //將DAO工廠寫成單態(tài)模式
??? private static DaoFactory df;
??? //DAO工廠的構(gòu)造器
??? private DaoFactory()throws Exception
??? {
??????? //使用SAXReader來負(fù)責(zé)解析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() ; )
??? ??? {
??????????? //每個子元素對應(yīng)一個DAO組件
??????? ??? Element em = (Element)it.next();
??????? ??? String id = em.attributeValue("id");
??? ??????? //獲取實(shí)現(xiàn)類
??????? ??? String impl = em.attributeValue("class");
??????????? //通過反射,根據(jù)類名創(chuàng)建DAO組件的實(shí)例
??????? ??? Class implClazz = Class.forName(impl);
??????? ??? Dao d = (Dao)implClazz.newInstance();
??????????? //將創(chuàng)建的DAO組件放入緩存池中
??????? ??? daoMap.put(id, d);
??? ??? }
??? }
??? //單態(tài)模式必須提供一個入口方法來創(chuàng)建DAO工廠的方法
??? public static DaoFactory instance()throws Exception
??? {
??????? //如果DAO工廠還未創(chuàng)建
??? ??? if (df == null)
??? ??? {
??????? ??? df = new DaoFactory();
??? ??? }
??? ??? return df;
??? }
??? //下面的方法用于根據(jù)DAO組件ID獲取DAO組件
??? public Dao getDao(String id)
??? {
??? ??? return daoMap.get(id);
??? }
}
通過上面的工廠類代碼可以看出,DAO工廠負(fù)責(zé)初始化所有的DAO組件。系統(tǒng)每增加一個DAO組件,無須再修改任何代碼,僅僅需要在daoContext.xml配置文件中增加配置即可。
注意:這種整合策略非常優(yōu)秀。可擴(kuò)展性很好,如果應(yīng)用需要增加一個DAO組件,只需要修改配置文件,并提供相應(yīng)的DAO組件實(shí)現(xiàn)即可。而且,如果有一天需要重構(gòu)DAO組件,只須提供修改過的DAO組件實(shí)現(xiàn)類,而業(yè)務(wù)邏輯組件無須任何改變。
業(yè)務(wù)邏輯組件代碼無須與DAO實(shí)現(xiàn)類耦合,業(yè)務(wù)邏輯組件的代碼面向DAO組件的接口編程,將業(yè)務(wù)邏輯組件和DAO組件的耦合降低到接口層次。
4.9.4 業(yè)務(wù)邏輯組件的工廠模式
與此類似的是,業(yè)務(wù)邏輯組件完全可以采用這種編程模式,業(yè)務(wù)邏輯組件的配置文件代碼如下:
<?xml version="1.0" encoding="GBK"?>
<appContext>
??? <!-- 配置應(yīng)用需要的業(yè)務(wù)邏輯組件,每個業(yè)務(wù)邏輯組件對應(yīng)一個app元素 -->
??? <app id="wawa" class="org.yeeku.service.impl.WawaServiceImpl"/>
</appContext>
業(yè)務(wù)邏輯組件工廠同樣可根據(jù)該配置文件來初始化所有業(yè)務(wù)邏輯組件,并將業(yè)務(wù)邏輯組件放入緩存池中,讓控制器與業(yè)務(wù)邏輯組件的耦合降低到接口層次。業(yè)務(wù)邏輯組件的工廠類代碼如下:
public class AppFactory
{
??? private Map<String , Object> appMap = new HashMap<String , Object>();
??? //業(yè)務(wù)邏輯組件工廠采用單態(tài)模式
??? private static AppFactory df;
??? //業(yè)務(wù)邏輯組件工廠的私有構(gòu)造器
??? private AppFactory()throws Exception
??? {
??????? //使用SAXReader來負(fù)責(zé)解析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元素對應(yīng)一個業(yè)務(wù)邏輯組件
??????????? Element em = (Element)it.next();
??????????? String id = em.attributeValue("id");
??????????? //根據(jù)配置文件指定的業(yè)務(wù)邏輯組件實(shí)現(xiàn)類來創(chuàng)建業(yè)務(wù)邏輯組件實(shí)例
??????????? String impl = em.attributeValue("class");
??????????? Class implClazz = Class.forName(impl);
??????????? Object d = implClazz.newInstance();
??????????? //將業(yè)務(wù)邏輯組件放入緩存池中
??????????? appMap.put(id , d);
??????? }
??? }
??? //單態(tài)模式必須提供入口方法,用于創(chuàng)建業(yè)務(wù)邏輯組件工廠
??? public static AppFactory instance()throws Exception
??? {
??????? //如果業(yè)務(wù)邏輯組件工廠為空
??????? if (df == null)
??????? {
??????????? df = new AppFactory();
??????? }
??????? return df;
??? }
??? //根據(jù)業(yè)務(wù)邏輯組件的id屬性獲取業(yè)務(wù)邏輯組件
??? public Object getApp(String id)
??? {
??????? //直接從緩存池中取出業(yè)務(wù)邏輯組件,并返回
??????? return appMap.get(id);
??? }
}
從某種程度上來講,這種方式與后來Spring的控制反轉(zhuǎn)(Inversion of Control,IoC)容器有異曲同工之妙,但Spring的IoC容器則提供了更多的功能。
上面的兩個類中都用到了一個ConstantsUtil,它僅用于保存一個全局變量,有一個public static的realPath屬性,該屬性用于保存應(yīng)用在服務(wù)器中的路徑。
posted on 2009-07-19 10:08
jadmin 閱讀(67)
評論(0) 編輯 收藏