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 閱讀(67)
評論(0) 編輯 收藏