<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 495,  comments - 11,  trackbacks - 0
     

    ?????? 數(shù)據(jù)庫(kù)的事務(wù)處理是在進(jìn)行數(shù)據(jù)庫(kù)應(yīng)用開(kāi)發(fā)中必須進(jìn)行處理的一個(gè)問(wèn)題。那么對(duì)于選擇Hibernate作為持久層組件,了解Hibernate的事務(wù)處理機(jī)制就顯得尤為重要了。

    事務(wù)的基本概念

    ?????? 事務(wù)(Transaction)是并發(fā)控制的基本單位。所謂的事務(wù),它是一個(gè)操作序列,這些操作要么都執(zhí)行,要么都不執(zhí)行,它是一個(gè)不可分割的工作單位。例如,銀行轉(zhuǎn)賬工作:從一個(gè)賬號(hào)扣款并使另一個(gè)賬號(hào)增款,這兩個(gè)操作要么都執(zhí)行,要么都不執(zhí)行。所以,應(yīng)該把它們看成一個(gè)事務(wù)。事務(wù)是數(shù)據(jù)庫(kù)維護(hù)數(shù)據(jù)一致性的單位,在每個(gè)事務(wù)結(jié)束時(shí),都能保持?jǐn)?shù)據(jù)一致性。

    ?????? 針對(duì)上面的描述可以看出,事務(wù)的提出主要是為了解決并發(fā)情況下保持?jǐn)?shù)據(jù)一致性的問(wèn)題。

    ?????? 事務(wù)具有以下4個(gè)基本特征。

    ●?? Atomic(原子性):事務(wù)中包含的操作被看做一個(gè)邏輯單元,這個(gè)邏輯單元中的操作要么全部成功,要么全部失敗。

    ●?? Consistency(一致性):只有合法的數(shù)據(jù)可以被寫(xiě)入數(shù)據(jù)庫(kù),否則事務(wù)應(yīng)該將其回滾到最初狀態(tài)。

    ●?? Isolation(隔離性):事務(wù)允許多個(gè)用戶對(duì)同一個(gè)數(shù)據(jù)進(jìn)行并發(fā)訪問(wèn),而不破壞數(shù)據(jù)的正確性和完整性。同時(shí),并行事務(wù)的修改必須與其他并行事務(wù)的修改相互獨(dú)立。

    ●?? Durability(持久性):事務(wù)結(jié)束后,事務(wù)處理的結(jié)果必須能夠得到固化。

    ?????? 數(shù)據(jù)庫(kù)肯定是要被廣大客戶所共享訪問(wèn)的,那么在數(shù)據(jù)庫(kù)操作過(guò)程中很可能出現(xiàn)以下幾種不確定情況。

    ●?? 更新丟失(Lost update):兩個(gè)事務(wù)都同時(shí)更新一行數(shù)據(jù),但是第二個(gè)事務(wù)卻中途失敗退出,導(dǎo)致對(duì)數(shù)據(jù)的兩個(gè)修改都失效了。這是因?yàn)橄到y(tǒng)沒(méi)有執(zhí)行任何的鎖操作,因此并發(fā)事務(wù)并沒(méi)有被隔離開(kāi)來(lái)。

    ●?? 臟讀取(Dirty Reads):一個(gè)事務(wù)開(kāi)始讀取了某行數(shù)據(jù),但是另外一個(gè)事務(wù)已經(jīng)更新了此數(shù)據(jù)但沒(méi)有能夠及時(shí)提交。這是相當(dāng)危險(xiǎn)的,因?yàn)楹芸赡芩械牟僮鞫急换貪L。

    ●?? 不可重復(fù)讀取(Non-repeatable Reads):一個(gè)事務(wù)對(duì)同一行數(shù)據(jù)重復(fù)讀取兩次,但是卻得到了不同的結(jié)果。例如,在兩次讀取的中途,有另外一個(gè)事務(wù)對(duì)該行數(shù)據(jù)進(jìn)行了修改,并提交。

    ●?? 兩次更新問(wèn)題(Second lost updates problem):無(wú)法重復(fù)讀取的特例。有兩個(gè)并發(fā)事務(wù)同時(shí)讀取同一行數(shù)據(jù),然后其中一個(gè)對(duì)它進(jìn)行修改提交,而另一個(gè)也進(jìn)行了修改提交。這就會(huì)造成第一次寫(xiě)操作失效。

    ●?? 虛讀(Phantom Reads):事務(wù)在操作過(guò)程中進(jìn)行兩次查詢,第二次查詢的結(jié)果包含了第一次查詢中未出現(xiàn)的數(shù)據(jù)(這里并不要求兩次查詢的SQL語(yǔ)句相同)。這是因?yàn)樵趦纱尾樵冞^(guò)程中有另外一個(gè)事務(wù)插入數(shù)據(jù)造成的。

    posted @ 2009-07-19 21:04 jadmin 閱讀(155) | 評(píng)論 (0)編輯 收藏

    6.5.4 使用HibernateCallBack

    HibernateTemplate還提供了一種更加靈活的方式來(lái)操作數(shù)據(jù)庫(kù),通過(guò)這種方式可以完全使用Hibernate的操作方式。HibernateTemplate的靈活訪問(wèn)方式可通過(guò)如下兩個(gè)方法完成:

    ?? ● Object execute(HibernateCallback action)。

    ?? ● List execute(HibernateCallback action)。

    這兩個(gè)方法都需要一個(gè)HibernateCallback的實(shí)例,HibernateCallback實(shí)例可在任何有效的Hibernate數(shù)據(jù)訪問(wèn)中使用。程序開(kāi)發(fā)者通過(guò)HibernateCallback,可以完全使用Hibernate靈活的方式來(lái)訪問(wèn)數(shù)據(jù)庫(kù),解決Spring封裝Hibernate后靈活性不足的缺陷。

    HibernateCallback是一個(gè)接口,該接口包含一個(gè)方法doInHibernate(org.hibernate. Session session),該方法只有一個(gè)參數(shù)Session。在開(kāi)發(fā)中提供HibernateCallback實(shí)現(xiàn)類時(shí),必須實(shí)現(xiàn)接口里包含的doInHibernate方法,在該方法體內(nèi)即可獲得Hibernate Session的引用,一旦獲得了Hibernate Session的引用,就可以完全以Hibernate的方式進(jìn)行數(shù)據(jù)庫(kù)訪問(wèn)。

    注意:doInHibernate方法內(nèi)可以訪問(wèn)Session,該Session對(duì)象是綁定在該線程的Session實(shí)例。該方法內(nèi)的持久層操作,與不使用Spring時(shí)的持久層操作完全相同。這保證了對(duì)于復(fù)雜的持久層訪問(wèn),依然可以使用Hibernate的訪問(wèn)方式。

    下面的代碼對(duì)HibernateDaoSupport類進(jìn)行擴(kuò)展(雖然Spring 2.0的HibernateTemplate提供了一個(gè)分頁(yè)方法setMaxResults,但僅此一個(gè)方法依然不能實(shí)現(xiàn)分頁(yè)查詢),這種擴(kuò)展主要是為該類增加了3個(gè)分頁(yè)查詢的方法,分頁(yè)查詢時(shí)必須直接調(diào)用Hibernate的Session完成,因此,必須借助于HibernateCallBack的幫助。

    public class YeekuHibernateDaoSupport extends HibernateDaoSupport

    {

    ??? /**

    ??? * 使用hql 語(yǔ)句進(jìn)行分頁(yè)查詢操作

    ??? * @param hql 需要查詢的hql語(yǔ)句

    ??? * @param offset 第一條記錄索引

    ??? * @param pageSize 每頁(yè)需要顯示的記錄數(shù)

    ??? * @return 當(dāng)前頁(yè)的所有記錄

    ??? */

    ??? public List findByPage(final String hql,

    ??????? final int offset, final int pageSize)

    ??? {

    ??????? //HibernateDaoSupport已經(jīng)包含了getHibernateTemplate()方法

    ??????? List list = getHibernateTemplate().executeFind(new
    ??????? HibernateCallback()

    ??????????? {

    ??????????????? public Object doInHibernate(Session session)

    ??????????????????? throws HibernateException, SQLException

    ??????????????? //該方法體內(nèi)以Hibernate方法進(jìn)行持久層訪問(wèn)

    ??????????????? {

    ??????????????????? List result = session.createQuery(hql)

    ??????????????????????????????????????? .setFirstResult(offset)

    ??????????????????????? ???????????????? .setMaxResults(pageSize)

    ??????????????????????????????????????? .list();

    ??????????????????? return result;

    ??????????????? }

    ??????????? });

    ??????? return list;

    ??? }

    ??? /**

    ??? * 使用hql 語(yǔ)句進(jìn)行分頁(yè)查詢操作

    ??? * @param hql 需要查詢的hql語(yǔ)句

    ??? * @param value 如果hql有一個(gè)參數(shù)需要傳入,value就是傳入的參數(shù)

    ??? * @param offset 第一條記錄索引

    ??? * @param pageSize 每頁(yè)需要顯示的記錄數(shù)

    ??? * @return 當(dāng)前頁(yè)的所有記錄

    ??? */

    ??? public List findByPage(final String hql , final Object value ,

    ??????? final int offset, final int pageSize)

    ??? {

    ??????? List list = getHibernateTemplate().executeFind(new
    ??????? HibernateCallback()

    ??????????? {

    ??????????????? public Object doInHibernate(Session session)

    ??????? ??????????? throws HibernateException, SQLException

    ??????????????? {

    ??????????????????? //下面查詢的是最簡(jiǎn)單的Hiberante HQL查詢

    ??????????????????? List result = session.createQuery(hql)

    ??????????????????????????????? ???????? .setParameter(0, value)

    ??????????????????????????????????????? .setFirstResult(offset)

    ??????????????????????? ???????????????? .setMaxResults(pageSize)

    ??????????????????? ??????????????????? .list();

    ??????????????????? return result;

    ??????????????? }

    ??????????? });

    ??????? return list;

    ??? }

    ??? /**

    ??? * 使用hql 語(yǔ)句進(jìn)行分頁(yè)查詢操作

    ??? * @param hql 需要查詢的hql語(yǔ)句

    ??? * @param values 如果hql有多個(gè)參數(shù)需要傳入,values就是傳入的參數(shù)數(shù)組

    ??? * @param offset 第一條記錄索引

    ??? * @param pageSize 每頁(yè)需要顯示的記錄數(shù)

    ??? * @return 當(dāng)前頁(yè)的所有記錄

    ??? */

    ??? public List findByPage(final String hql, final Object[] values,

    ??????? final int offset, final int pageSize)

    ??? {

    ??????? List list = getHibernateTemplate().executeFind(new
    ??????? HibernateCallback()

    ??????????? {

    ??????????????? public Object doInHibernate(Session session)

    ??????????????????? throws HibernateException, SQLException

    ??????????????? {

    ??????????????????? Query query = session.createQuery(hql);

    ??????????????????? for (int i = 0 ; i < values.length ; i++)

    ??????????????????? {

    ??????????????????????? query.setParameter( i, values[i]);

    ??????????????????? }

    ??????????????????? List result = query.setFirstResult(offset)

    ??????????????????????? ?????????????? .setMaxResults(pageSize)

    ??????????????????????????????????? ?? .list();

    ??????????????????? return result;

    ??????????????? }

    ??????????? });

    ??????? return list;

    ??? }

    }

    在上面的代碼實(shí)現(xiàn)中,直接使用了getHibernateTemplate()方法,這個(gè)方法由Hibernate- DaoSupport提供。而YeekuHibernateDaoSupport是HibernateDaoSupport的子類,因此,可以直接使用該方法。

    當(dāng)實(shí)現(xiàn)doInHibernate(Session session)方法時(shí),完全以Hibernate的方式進(jìn)行數(shù)據(jù)庫(kù)訪問(wèn),這樣保證了Hibernate進(jìn)行數(shù)據(jù)庫(kù)訪問(wèn)的靈活性。

    注意:Spring提供的XxxTemplate和XxxCallBack互為補(bǔ)充,二者體現(xiàn)了Spring框架設(shè)計(jì)的用心良苦:XxxTemplate對(duì)通用操作進(jìn)行封裝,而XxxCallBack解決了封裝后靈活性不足的缺陷。

    6.5.5 實(shí)現(xiàn)DAO組件

    為了實(shí)現(xiàn)DAO組件,Spring提供了大量的XxxDaoSupport類,這些DAO支持類對(duì)于實(shí)現(xiàn)DAO組件大有幫助,因?yàn)檫@些DAO支持類已經(jīng)完成了大量基礎(chǔ)性工作。

    Spring為Hibernate的DAO提供了工具類HibernateDaoSupport。該類主要提供如下兩個(gè)方法以方便DAO的實(shí)現(xiàn):

    ?? ● public final HibernateTemplate getHibernateTemplate()。

    ?? ● public final void setSessionFactory(SessionFactory sessionFactory)。

    其中,setSessionFactory方法可用于接收Spring的ApplicationContext的依賴注入,可接收配置在Spring的SessionFactory實(shí)例,getHibernateTemplate方法用于返回通過(guò)SessionFactory產(chǎn)生的HibernateTemplate實(shí)例,持久層訪問(wèn)依然通過(guò)HibernateTemplate實(shí)例完成。

    下面實(shí)現(xiàn)的DAO組件繼承了Spring提供的HibernateDaoSupport類,依然實(shí)現(xiàn)了PersonDao接口,其功能與前面提供的PersonDao實(shí)現(xiàn)類完全相同。其代碼如下:

    public class PersonDaoHibernate extends HibernateDaoSupport implements PersonDao

    {

    ??? /**

    ???? * 加載人實(shí)例

    ???? * @param id 需要加載的Person實(shí)例的主鍵值

    ???? * @return 返回加載的Person實(shí)例

    ???? */

    ??? public Person get(int id)

    ??? {

    ??????? return (Person)getHibernateTemplate().get(Person.class, new
    ??????? Integer(id));

    ??? }

    ??? /**

    ???? * 保存人實(shí)例

    ???? * @param person 需要保存的Person實(shí)例

    ???? */???

    ??? public void save(Person person)

    ??? {

    ??????? getHibernateTemplate().save(person);

    ??? }

    ??? /**

    ???? * 修改Person實(shí)例

    ???? * @param person 需要修改的Person實(shí)例

    ???? */

    ??? public void update(Person person)

    ??? {

    ??????? getHibernateTemplate().update(person);

    ??? }

    ??? /**

    ???? * 刪除Person實(shí)例

    ???? * @param id 需要?jiǎng)h除的Person的id

    ???? */

    ??? public void delete(int id)

    ??? {

    ??????? getHibernateTemplate().delete(getHibernateTemplate().
    ??????? get(Person.class, new Integer(id)));

    ??? }

    ??? /**

    ???? * 刪除Person實(shí)例

    ???? * @param person 需要?jiǎng)h除的Person實(shí)例

    ???? */

    ??? public void delete(Person person)

    ??? {

    ??????? getHibernateTemplate().delete(person);

    ??? }

    ??? /**

    ???? * 根據(jù)用戶名查找Person

    ???? * @param name 用戶名

    ???? * @return 用戶名對(duì)應(yīng)的全部用戶

    ???? */

    ??? public List findByPerson(String name)

    ??? {

    ??????? return getHibernateTemplate().find("from Person p where p.name
    ??????? like ?" , name);???????

    ??? }

    ??? /**

    ??? * 返回全部的Person實(shí)例

    ??? * @return 全部的Person實(shí)例

    ??? */

    ??? public List findAllPerson()

    ??? {

    ??????? return getHibernateTemplate().find("from Person ");

    ??? }

    }

    上面的代碼與前面的PersonDAOImpl對(duì)比會(huì)發(fā)現(xiàn),代碼量大大減少。事實(shí)上,DAO的實(shí)現(xiàn)依然借助于HibernateTemplate的模板訪問(wèn)方式,只是HibernateDaoSupport將依賴注入SessionFactory的工作已經(jīng)完成,獲取HibernateTemplate的工作也已完成。該DAO的配置必須依賴于SessionFactory,配置文件與前面部署DAO組件的方式完全相同,此處不再贅述。

    在繼承HibernateDaoSupport的DAO實(shí)現(xiàn)里,Hibernate Session的管理完全不需要打開(kāi)代碼,而由Spring來(lái)管理。Spring會(huì)根據(jù)實(shí)際的操作,采用“每次事務(wù)打開(kāi)一次session”的策略,自動(dòng)提高數(shù)據(jù)庫(kù)訪問(wèn)的性能。

    6.5.6 使用IoC容器組裝各種組件

    至此為止,J2EE應(yīng)用所需要的各種組件都已經(jīng)出現(xiàn)了,從MVC層的控制器組件,到業(yè)務(wù)邏輯組件,以及持久層的DAO組件,已經(jīng)全部成功實(shí)現(xiàn)。應(yīng)用程序代碼并未將這些組件耦合在一起,代碼中都是面向接口編程,因此必須利用Spring的IoC容器將他們組合在一起。

    從用戶角度來(lái)看,用戶發(fā)出HTTP請(qǐng)求,當(dāng)MVC框架的控制器組件攔截到用戶請(qǐng)求時(shí),將調(diào)用系統(tǒng)的業(yè)務(wù)邏輯組件,而業(yè)務(wù)邏輯組件則調(diào)用系統(tǒng)的DAO組件,而DAO組件則依賴于SessionFactory和DataSource等底層組件實(shí)現(xiàn)數(shù)據(jù)庫(kù)訪問(wèn)。

    從系統(tǒng)實(shí)現(xiàn)角度來(lái)看,IoC容器先創(chuàng)建SessionFactory和DataSource等底層組件,然后將這些底層組件注入給DAO組件,提供一個(gè)完整的DAO組件,并將此DAO組件注入給業(yè)務(wù)邏輯組件,從而提供一個(gè)完整的業(yè)務(wù)邏輯組件,而業(yè)務(wù)邏輯組件又被注入給控制器組件,控制器組件負(fù)責(zé)攔截用戶請(qǐng)求,并將處理結(jié)果呈現(xiàn)給用戶——這一系列的銜接都由Spring的IoC容器提供實(shí)現(xiàn)。

    下面給出關(guān)于如何在容器中配置J2EE組件的大致模板,其模板代碼如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->

    ??? <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    ??? destroy-method="close">

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->

    ??????? <property name="driverClass" value="com.mysql.jdbc.Driver"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->

    ??????? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶名 -->

    ??????? <property name="user" value="root"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->

    ??????? <property name="password" value="32147"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最大連接數(shù) -->

    ??????? <property name="maxPoolSize" value="40"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最小連接數(shù) -->

    ??????? <property name="minPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的初始化連接數(shù) -->

    ??????? <property name="initialPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的連接最大空閑時(shí)間 -->

    ??????? <property name="maxIdleTime" value="20"/>

    ??? </bean>

    ??? <!-- 定義Hibernate的SessionFactory Bean -->

    ??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    ??? LocalSessionFactoryBean">

    ??????? <!-- 依賴注入數(shù)據(jù)源,注入的正是上文中定義的dataSource -->

    ??????? <property name="dataSource" ref="dataSource"/>

    ??????? <!-- mappingResources屬性用來(lái)列出全部映射文件 -->

    ??????? <property name="mappingResources">

    ??????????? <list>

    ??????????????? <!-- 以下用來(lái)列出所有的PO映射文件 -->

    ??????????????? <value>lee/Person.hbm.xml</value>

    ??????????????? <!-- 此處還可列出更多的PO映射文件 -->

    ??????????? </list>

    ??????? </property>

    ????????? <!-- 定義Hibernate的SessionFactory屬性 -->

    ??????? <property name="hibernateProperties">

    ??????? ???? <props>

    ??????????????? <!-- 指定Hibernate的連接方言 -->

    ??????????? ??? <prop key="hibernate.dialect">org.hibernate.dialect.
    ??????????????? MySQLDialect</prop>

    ??????????????? <!-- 指定啟動(dòng)應(yīng)用時(shí),是否根據(jù)Hibernate映射文件創(chuàng)建數(shù)據(jù)表 -->

    ??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>

    ??????? ???? </props>

    ??????? </property>

    ??? </bean>

    ??? <!-- 配置Person持久化類的DAO Bean -->

    ??? <bean id="personDao" class="lee.PersonDaoImpl">

    ??????? <!-- 采用依賴注入來(lái)傳入SessionFactory的引用 -->

    ??????? <property name="sessionFactory" ref="sessionFactory"/>

    ??? </bean>

    ??? <!-- 下面能以相同的方式配置更多的持久化Bean -->

    ??? ...

    ??? <bean id="myService" class="lee.MyServiceImp">

    ??????? <!-- 注入業(yè)務(wù)邏輯組件所必需的DAO組件 -->

    ??? ??? <property name="peronDdao" ref=" personDao "/>

    ??????? <!-- 此處可采用依賴注入更多的DAO組件 -->

    ??????? ...

    ??? </bean>

    ??? <!-- 配置控制器Bean,設(shè)置起作用域?yàn)镽equest -->

    ??? <bean name="/login" class="lee.LoginAction" scope="request">

    ??????? <!-- 依賴注入控制器所必需的業(yè)務(wù)邏輯組件 -->

    ??????? <property name="myService" ref=" myService "/>

    ??? </bean>

    </beans>

    在上面的配置文件中,同時(shí)配置了控制器Bean、業(yè)務(wù)邏輯組件Bean、DAO組件Bean以及一些基礎(chǔ)資源Bean。各組件的組織被解耦到配置文件中,而不是在代碼層次的低級(jí)耦合。

    當(dāng)客戶端的HTTP請(qǐng)求向/login.do發(fā)送請(qǐng)求時(shí),將被容器中的lee.LoginAction攔截,LoginAction調(diào)用myService Bean,myService Bean則調(diào)用personDao等系列DAO組件,整個(gè)流程將系統(tǒng)中的各組件有機(jī)地組織在一起。

    注意:在實(shí)際應(yīng)用中,很少會(huì)將DAO組件、業(yè)務(wù)邏輯組件以及控制組件都配置在同一個(gè)文件中。而是在不同配置文件中,配置相同一組J2EE應(yīng)用組件。

    6.5.7 使用聲明式事務(wù)

    在上面的配置文件中,部署了控制器組件、業(yè)務(wù)邏輯組件、DAO組件,幾乎可以形成一個(gè)完整的J2EE應(yīng)用。但有一個(gè)小小的問(wèn)題:事務(wù)控制。系統(tǒng)沒(méi)有任何事務(wù)邏輯,沒(méi)有事務(wù)邏輯的應(yīng)用是不可想象的。

    Spring提供了非常簡(jiǎn)潔的聲明式事務(wù)控制,只需要在配置文件中增加事務(wù)控制片段,業(yè)務(wù)邏輯代碼無(wú)須任何改變。Spring的聲明式事務(wù)邏輯,甚至支持在不同事務(wù)策略之間切換。

    配置Spring聲明式事務(wù)時(shí),通常推薦使用BeanNameAutoProxyCreator自動(dòng)創(chuàng)建事務(wù)代理。通過(guò)這種自動(dòng)事務(wù)代理的配置策略,增加業(yè)務(wù)邏輯組件,只需要在BeanNameAutoProxyCreator Bean配置中增加一行即可,從而避免了增量式配置。

    在上面的配置模板文件中增加如下配置片段,系統(tǒng)的myService業(yè)務(wù)邏輯組件將變成事務(wù)代理Bean,從而為業(yè)務(wù)邏輯方法增加事務(wù)邏輯。

    ??? <!-- 配置Hibernate的局部事務(wù)管理器 -->

    ??? <!-- 使用HibernateTransactionManager類,該類是PlatformTransactionManager
    ??? 接口,針對(duì)采用Hibernate持久化連接的特定實(shí)現(xiàn) -->

    ??? <bean id="transactionManager"

    ????? ??? class="org.springframework.orm.hibernate3.
    ???????? HibernateTransactionManager">

    ??????? <!-- HibernateTransactionManager Bean需要依賴注入一個(gè)SessionFactory
    ??????? bean的引用 -->

    ??? ???? <property name="sessionFactory" ref="sessionFactory"/>

    ??? </bean>

    ??? <!-- 配置事務(wù)攔截器Bean -->

    ??? <bean id="transactionInterceptor"

    ??????? class="org.springframework.transaction.interceptor.
    ??????? TransactionInterceptor">

    ??????? <!-- 事務(wù)攔截器bean需要依賴注入一個(gè)事務(wù)管理器 -->

    ??????? <property name="transactionManager" ref="transactionManager"/>

    ??????? <property name="transactionAttributes">

    ??????????? <!-- 下面定義事務(wù)傳播屬性 -->

    ??????????? <props>

    ??????????????? <prop key="insert*">PROPAGATION_REQUIRED</prop>

    ??????????????? <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

    ??????????????? <prop key="*">PROPAGATION_REQUIRED</prop>

    ??????????? </props>

    ??????? </property>

    ??? </bean>

    ??? <!-- 定義BeanNameAutoProxyCreator的Bean后處理器 -->

    ??? <bean class="org.springframework.aop.framework.autoproxy.
    ??? BeanNameAutoProxyCreator">

    ??? <!-- 指定對(duì)滿足哪些bean name的bean自動(dòng)生成業(yè)務(wù)代理 -->

    ??? ??? <property name="beanNames">

    ??????????? <!-- 下面是所有需要自動(dòng)創(chuàng)建事務(wù)代理的Bean -->

    ??????????? <list>

    ??????????????? <value>myService</value>

    ??????????????? <!-- 下面還可增加需要增加事務(wù)邏輯的業(yè)務(wù)邏輯Bean -->

    ??????????????? ...

    ??????????? </list>

    ??????????? <!-- 此處可增加其他需要自動(dòng)創(chuàng)建事務(wù)代理的Bean -->

    ??? ??? </property>

    ??????? <!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->

    ??????? <property name="interceptorNames">

    ??????????? <list>

    ??????????????? <value>transactionInterceptor</value>

    ??????????????? <!-- 此處可增加其他新的Interceptor -->

    ??????????? </list>

    ??????? </property>

    ??? </bean>

    一旦增加了如上的配置片段,系統(tǒng)中的業(yè)務(wù)邏輯方法就有了事務(wù)邏輯。這種聲明式事務(wù)配置方式可以在不同的事務(wù)策略之間自由切換。

    提示:盡量使用聲明式事務(wù)配置方式,而不要在代碼中完成事務(wù)邏輯。

    posted @ 2009-07-19 10:24 jadmin 閱讀(381) | 評(píng)論 (0)編輯 收藏

    6.5 Spring整合Hibernate

    時(shí)至今日,可能極少有J2EE應(yīng)用會(huì)直接以JDBC方式進(jìn)行持久層訪問(wèn)。畢竟,用面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言來(lái)訪問(wèn)關(guān)系型數(shù)據(jù)庫(kù),是一件讓人沮喪的事情。大部分時(shí)候,J2EE應(yīng)用都會(huì)以O(shè)RM框架來(lái)進(jìn)行持久層訪問(wèn),在所有的ORM框架中,Hibernate以其靈巧、輕便的封裝贏得了眾多開(kāi)發(fā)者的青睞。

    Spring具有良好的開(kāi)放性,能與大部分ORM框架良好整合。下面將詳細(xì)介紹Spring與Hibernate的整合。

    6.5.1 Spring提供的DAO支持

    DAO模式是一種標(biāo)準(zhǔn)的J2EE設(shè)計(jì)模式,DAO模式的核心思想是,所有的數(shù)據(jù)庫(kù)訪 問(wèn),都通過(guò)DAO組件完成,DAO組件封裝了數(shù)據(jù)庫(kù)的增、刪、改等原子操作。而業(yè)務(wù)邏輯組件則依賴于DAO組件提供的數(shù)據(jù)庫(kù)原子操作,完成系統(tǒng)業(yè)務(wù)邏輯的實(shí)現(xiàn)。

    對(duì)于J2EE應(yīng)用的架構(gòu),有非常多的選擇,但不管細(xì)節(jié)如何變換,J2EE應(yīng)用都大致可分為如下3層:

    ?? ● 表現(xiàn)層。

    ?? ● 業(yè)務(wù)邏輯層。

    ?? ● 數(shù)據(jù)持久層。

    輕量級(jí)J2EE架構(gòu)以Spring IoC容器為核心,承上啟下。其向上管理來(lái)自表現(xiàn)層的Action,向下管理業(yè)務(wù)邏輯層組件,同時(shí)負(fù)責(zé)管理業(yè)務(wù)邏輯層所需的DAO對(duì)象。各層之間負(fù)責(zé)傳值的是值對(duì)象,也就是JavaBean實(shí)例。

    圖6.5精確地描繪了輕量級(jí)J2EE架構(gòu)的大致情形。

    DAO組件是整個(gè)J2EE應(yīng)用的持久層訪問(wèn)的重要組件,每個(gè)J2EE應(yīng)用的底層實(shí)現(xiàn)都難以離開(kāi)DAO組件的支持。Spring對(duì)實(shí)現(xiàn)DAO組件提供了許多工具類,系統(tǒng)的DAO組件可通過(guò)繼承這些工具類完成,從而可以更加簡(jiǎn)便地實(shí)現(xiàn)DAO組件。

    Spring的DAO支持,允許使用相同的方式、不同的數(shù)據(jù)訪問(wèn)技術(shù),如JDBC、Hibernate或JDO。Spring的DAO在不同的持久層訪問(wèn)技術(shù)上提供抽象,應(yīng)用的持久層訪問(wèn)基于Spring的DAO抽象。因此,應(yīng)用程序可以在不同的持久層技術(shù)之間切換。

    Spring提供了一系列的抽象類,這些抽象將被作為應(yīng)用中DAO實(shí)現(xiàn)類的父類。通過(guò)繼承這些抽象類,Spring簡(jiǎn)化了DAO的開(kāi)發(fā)步驟,能以一致的方式使用數(shù)據(jù)庫(kù)訪問(wèn)技術(shù)。不管底層采用JDBC、JDO或Hibernate,應(yīng)用中都可采用一致的編程模型。

    圖6.5 輕量級(jí)J2EE應(yīng)用架構(gòu)

    應(yīng)用的DAO類繼承這些抽象類,會(huì)大大簡(jiǎn)化應(yīng)用的開(kāi)發(fā)。最大的好處是,繼承這些抽象類的DAO能以一致的方式訪問(wèn)數(shù)據(jù)庫(kù),意味著應(yīng)用程序可以在不同的持久層訪問(wèn)技術(shù)中切換。

    除此之外,Spring提供了一致的異常抽象,將原有的Checked異常轉(zhuǎn)換包裝成Runtime異常,因而,編碼時(shí)無(wú)須捕獲各種技術(shù)中特定的異常。Spring DAO體系中的異常,都繼承DataAccessException,而DataAccessException異常是Runtime的,無(wú)須顯式捕捉。通過(guò)DataAccessException的子類包裝原始異常信息,從而保證應(yīng)用程序依然可以捕捉到原始異常信息。

    Spring提供了多種數(shù)據(jù)庫(kù)訪問(wèn)技術(shù)的DAO支持,包括Hibernate、JDO、TopLink、iBatis、OJB等。Spring可以使用相同的訪問(wèn)模式、不同的數(shù)據(jù)庫(kù)訪問(wèn)技術(shù)。就Hibernate的持久層訪問(wèn)技術(shù)而言,Spring提供了如下3個(gè)工具類(或接口)來(lái)支持DAO組件的實(shí)現(xiàn):

    ?? ● HibernateDaoSupport。

    ?? ● HibernateTemplate。

    ?? ● HibernateCallBack。

    6.5.2 管理Hibernate的SessionFactory

    前面介紹Hibernate時(shí)已經(jīng)知道,在通過(guò)Hibernate進(jìn)行持久層訪問(wèn)時(shí),Hibernate的SessionFactory是一個(gè)非常重要的對(duì)象,它是單個(gè)數(shù)據(jù)庫(kù)映射關(guān)系編譯后的內(nèi)存鏡像。大部分情況下,一個(gè)J2EE應(yīng)用對(duì)應(yīng)一個(gè)數(shù)據(jù)庫(kù),也即對(duì)應(yīng)一個(gè)SessionFactory對(duì)象。

    在純粹的Hibernate訪問(wèn)中,應(yīng)用程序需要手動(dòng)創(chuàng)建SessionFactory實(shí)例,可想而知,這不是一個(gè)優(yōu)秀的策略。在實(shí)際開(kāi)發(fā)中,希望以一種聲明式的方式管理SessionFactory實(shí)例,直接以配置文件來(lái)管理SessionFactory實(shí)例,在示范Struts的PlugIn擴(kuò)展點(diǎn)時(shí),大致示范了這種方式(請(qǐng)參閱2.12.1節(jié)的內(nèi)容)。

    Spring的IoC容器則提供了更好的管理方式,它不僅能以聲明式的方式配置Session- Factory實(shí)例,也可充分利用IoC容器的作用,為SessionFactory注入數(shù)據(jù)源引用。

    下面是Spring配置文件中配置Hibernate SessionFactory的示范代碼:

    <?xml version="1.0" encoding="GBK"?>

    <!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->

    ??? <bean id="dataSource" class="com.mchange.v2.c3p0. ComboPooledDataSource"
    ??? destroy-method="close">

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->

    ??????? <property name="driverClass" value="com.mysql.jdbc.Driver"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->

    ??????? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶名 -->

    ??????? <property name="user" value="root"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->

    ??????? <property name="password" value="32147"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最大連接數(shù) -->

    ??????? <property name="maxPoolSize" value="40"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最小連接數(shù) -->

    ??????? <property name="minPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的初始化連接數(shù) -->

    ??????? <property name="initialPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的連接最大空閑時(shí)間 -->

    ??????? <property name="maxIdleTime" value="20"/>

    ??? </bean>

    ??? <!-- 定義Hibernate的SessionFactory -->

    ??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    ??? LocalSessionFactoryBean">

    ??????? <!-- 依賴注入數(shù)據(jù)源,正是上文定義的dataSource -->

    ??????? <property name="dataSource" ref="dataSource"/>

    ??????? <!-- mappingResources屬性用來(lái)列出全部映射文件 -->

    ??????? <property name="mappingResources">

    ??????????? <list>

    ??????????????? <!-- 以下用來(lái)列出所有的PO映射文件 -->

    ??????????????? <value>lee/MyTest.hbm.xml</value>

    ??????????? </list>

    ??????? </property>

    ????????? <!-- 定義Hibernate的SessionFactory屬性 -->

    ??????? <property name="hibernateProperties">

    ??????? ???? <props>

    ??????????????? <!-- 指定Hibernate的連接方言 -->

    ??????????? ??? <prop key="hibernate.dialect">org.hibernate.dialect.
    ??????????????? MySQLDialect</prop>

    ??????????????? <!-- 配置啟動(dòng)應(yīng)用時(shí),是否根據(jù)Hibernate映射自動(dòng)創(chuàng)建數(shù)據(jù)表 -->

    ??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>

    ?????????? </props>

    ??????? </property>

    ??? </bean>

    </beans>

    一旦在Spring的IoC容器中配置了SessionFactory Bean,它將隨應(yīng)用的啟動(dòng)而加載,并可以充分利用IoC容器的功能,將SessionFactory Bean注入任何Bean,比如DAO組件。一旦DAO組件獲得了SessionFactory Bean的引用,就可以完成實(shí)際的數(shù)據(jù)庫(kù)訪問(wèn)。

    當(dāng)然,Spring也支持訪問(wèn)容器數(shù)據(jù)源。如果需要使用容器數(shù)據(jù)源,可將數(shù)據(jù)源Bean修改成如下配置:

    <!-- 此處配置JNDI數(shù)據(jù)源 -->

    <bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">

    ??? <property name="jndiName">

    ??????? <!-- 指定數(shù)據(jù)源的JNDI -->

    ??????? <value>java:comp/env/jdbc/myds</value>

    ??? </property>

    </bean>

    可見(jiàn),以聲明式的方式管理SessionFactory實(shí)例,可以讓?xiě)?yīng)用在不同數(shù)據(jù)源之間切換。如果應(yīng)用更換數(shù)據(jù)庫(kù)等持久層資源,只需對(duì)配置文件進(jìn)行簡(jiǎn)單修改即可。

    提示:以聲明式的方式管理SessionFactory,非常類似于早期將數(shù)據(jù)庫(kù)服務(wù)的相關(guān)信息放在web.xml文件中進(jìn)行配置。這種方式是為了提供更好的適應(yīng)性,當(dāng)持久層服務(wù)需要更改時(shí),應(yīng)用代碼無(wú)須任何改變。

    6.5.3 使用HibernateTemplate

    HibernateTemplate提供持久層訪問(wèn)模板,使用HibernateTemplate無(wú)須實(shí)現(xiàn)特定接口,它只需要提供一個(gè)SessionFactory的引用就可執(zhí)行持久化操作。SessionFactory對(duì)象既可通過(guò)構(gòu)造參數(shù)傳入,也可通過(guò)設(shè)值方式傳入。HibernateTemplate提供如下3個(gè)構(gòu)造函數(shù):

    ?? ● HibernateTemplate()。

    ?? ● HibernateTemplate(org.hibernate.SessionFactory sessionFactory)。

    ?? ● HibernateTemplate(org.hibernate.SessionFactory sessionFactory, boolean allowCreate)。

    第一個(gè)構(gòu)造函數(shù),構(gòu)造一個(gè)默認(rèn)的HibernateTemplate實(shí)例。因此,使用Hibernate- Template實(shí)例之前,還必須使用方法setSessionFactory(SessionFactory sessionFactory)來(lái)為HibernateTemplate傳入SessionFactory的引用。

    第二個(gè)構(gòu)造函數(shù),在構(gòu)造時(shí)已經(jīng)傳入SessionFactory引用。

    第三個(gè)構(gòu)造函數(shù),其boolean型參數(shù)表明,如果當(dāng)前線程已經(jīng)存在一個(gè)非事務(wù)性的Session,是否直接返回此非事務(wù)性的Session。

    在Web應(yīng)用中,通常啟動(dòng)時(shí)自動(dòng)加載ApplicationContext,SessionFactory和DAO對(duì)象都處在Spring上下文管理下,因此無(wú)須在代碼中顯式設(shè)置,可采用依賴注入完成Session- Factory和DAO的解耦,依賴關(guān)系通過(guò)配置文件來(lái)設(shè)置,如下所示:

    <?xml version="1.0" encoding="GBK"?>

    <!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->

    ??? <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    ??? destroy-method="close">

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->

    ??????? <property name="driverClass" value="com.mysql.jdbc.Driver"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->

    ??????? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶名 -->

    ??????? <property name="user" value="root"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->

    ??????? <property name="password" value="32147"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最大連接數(shù) -->

    ??????? <property name="maxPoolSize" value="40"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最小連接數(shù) -->

    ??????? <property name="minPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的初始化連接數(shù) -->

    ??????? <property name="initialPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的連接最大空閑時(shí)間 -->

    ??????? <property name="maxIdleTime" value="20"/>

    ??? </bean>

    ??? <!-- 定義Hibernate的SessionFactory Bean -->

    ??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    ??? LocalSessionFactoryBean">

    ??????? <!-- 依賴注入數(shù)據(jù)源,注入的正是上文中定義的dataSource -->

    ??????? <property name="dataSource" ref="dataSource"/>

    ??????? <!-- mappingResources屬性用來(lái)列出全部映射文件 -->

    ??????? <property name="mappingResources">

    ??????????? <list>

    ??????????????? <!-- 以下用來(lái)列出所有的PO映射文件 -->

    ??????????????? <value>lee/Person.hbm.xml</value>

    ??????????? </list>

    ??????? </property>

    ????????? <!-- 定義Hibernate的SessionFactory屬性 -->

    ??????? <property name="hibernateProperties">

    ??????? ???? <props>

    ??????????????? <!-- 指定Hibernate的連接方言 -->

    ??????????? ??? <prop key="hibernate.dialect">org.hibernate.dialect.
    ??????????????? MySQLDialect</prop>

    ??????????????? <!-- 指定啟動(dòng)應(yīng)用時(shí),是否根據(jù)Hibernate映射文件創(chuàng)建數(shù)據(jù)表 -->

    ??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>

    ??????? ???? </props>

    ??????? </property>

    ??? </bean>

    ??? <!-- 配置Person持久化類的DAO bean -->

    ??? <bean id="personDao" class="lee.PersonDaoImpl">

    ??? ??? <!-- 采用依賴注入來(lái)傳入SessionFactory的引用 -->

    ??????? <property name="sessionFactory" ref="sessionFactory"/>

    ??? </bean>

    </beans>

    在PersonDao組件中,所有的持久化操作都通過(guò)HibernateTemplate實(shí)例完成,而HibernateTemplate操作數(shù)據(jù)庫(kù)非常簡(jiǎn)潔,大部分CRUD操作都可通過(guò)一行代碼解決問(wèn)題。下面介紹如何通過(guò)HibernateTemplate進(jìn)行持久層訪問(wèn)。

    HibernateTemplate提供了非常多的常用方法來(lái)完成基本的操作,比如通常的增加、刪除、修改、查詢等操作,Spring 2.0更增加了對(duì)命名SQL查詢的支持,也增加了對(duì)分頁(yè)的支持。大部分情況下,使用Hibernate的常規(guī)用法,就可完成大多數(shù)DAO對(duì)象的CRUD操作。下面是HibernateTemplate的常用方法簡(jiǎn)介:

    ?? ● void delete(Object entity),刪除指定持久化實(shí)例。

    ?? ● deleteAll(Collection entities),刪除集合內(nèi)全部持久化類實(shí)例。

    ?? ● find(String queryString),根據(jù)HQL查詢字符串來(lái)返回實(shí)例集合。

    ?? ● findByNamedQuery(String queryName),根據(jù)命名查詢返回實(shí)例集合。

    ?? ● get(Class entityClass, Serializable id),根據(jù)主鍵加載特定持久化類的實(shí)例。

    ?? ● save(Object entity),保存新的實(shí)例。

    ?? ● saveOrUpdate(Object entity),根據(jù)實(shí)例狀態(tài),選擇保存或者更新。

    ?? ● update(Object entity),更新實(shí)例的狀態(tài),要求entity是持久狀態(tài)。

    ?? ● setMaxResults(int maxResults),設(shè)置分頁(yè)的大小。

    下面是一個(gè)完整DAO類的源代碼:

    public class PersonDaoImpl implements PersonDao

    {

    ??? //執(zhí)行持久化操作的HibernateTemplate實(shí)例

    ??? private HibernateTemplate ht = null;

    ??? private SessionFactory sessionFactory;

    ??? //該DAO組件持久化操作所需的SessionFactory對(duì)象

    ??? public void setSessionFactory(SessionFactory sessionFactory)

    ??? {

    ??????? this.sessionFactory = sessionFactory;

    ??? }

    ??? //用于根據(jù)SessionFactory實(shí)例返回HibernateTemplate實(shí)例的方法

    ??? private HibernateTemplate getHibernateTemplate()

    ??? {

    ??????? if (ht == null)

    ??????? {

    ??????????? ht = new HibernateTemplate(sessionFactory);

    ??????? }

    ??????? return ht;

    ??? }

    ??? /**

    ???? * 加載人實(shí)例

    ???? * @param id 需要加載的Person實(shí)例的主鍵值

    ???? * @return 返回加載的Person實(shí)例

    ???? */

    ??? public Person get(int id)

    ??? {

    ??????? return (Person)getHibernateTemplate().get(Person.class, new
    ??????? Integer(id));

    ??? }

    ??? /**

    ???? * 保存人實(shí)例

    ???? * @param person 需要保存的Person實(shí)例

    ???? */???

    ??? public void save(Person person)

    ??? {

    ??????? getHibernateTemplate().save(person);

    ??? }

    ??? /**

    ???? * 修改Person實(shí)例

    ???? * @param person 需要修改的Person實(shí)例

    ???? */

    ??? public void update(Person person)

    ??? {

    ??????? getHibernateTemplate().update(person);

    ??? }

    ??? /**

    ???? * 刪除Person實(shí)例

    ???? * @param id 需要?jiǎng)h除的Person的id

    ???? */

    ??? public void delete(int id)

    ??? {

    ??????? getHibernateTemplate().delete(getHibernateTemplate().get(Person.
    ??????? class,new Integer(id)));

    ??? }

    ??? /**

    ???? * 刪除Person實(shí)例

    ???? * @param person 需要?jiǎng)h除的Person實(shí)例

    ???? */

    ?? public void delete(Person person)

    ??? {

    ??????? getHibernateTemplate().delete(person);

    ??? }

    ??? /**

    ???? * 根據(jù)用戶名查找Person

    ???? * @param name 用戶名

    ???? * @return 用戶名對(duì)應(yīng)的全部用戶

    ???? */

    ??? public List findByName(String name)

    ??? {

    ??????? return getHibernateTemplate().find("from Person p where p.name
    ??????? like ?" , name);

    ??? }

    ??? /**

    ??? * 返回全部的Person實(shí)例

    ??? * @return 全部的Person實(shí)例

    ??? */

    ??? public List findAllPerson()

    ??? {

    ??????? return getHibernateTemplate().find("from Person ");

    ??? }

    }

    通過(guò)上面實(shí)現(xiàn)DAO組件的代碼可以看出,通過(guò)HibernateTemplate進(jìn)行持久層訪問(wèn)的代碼如此清晰,大部分CRUD操作一行代碼即可完成,完全無(wú)須Hibernate訪問(wèn)那些繁瑣的步驟。而且,一旦DAO組件獲得了SessionFactory的引用,即可很輕易地創(chuàng)建HibernateTemplate實(shí)例。

    提示:HibernateTemplate是Spring眾多模板工具類之一,Spring正是通過(guò)這種簡(jiǎn)便地封裝,完成了開(kāi)發(fā)中大量需要重復(fù)執(zhí)行的工作。

    posted @ 2009-07-19 10:24 jadmin 閱讀(677) | 評(píng)論 (0)編輯 收藏

    Struts的plug-in配置部分明確指出,Spring的配置文件有兩個(gè):applicationContext.xml和action-servlet.xml。其實(shí),完全可以使用一個(gè)配置文件。通常,習(xí)慣將Action Bean配置在控制器的context內(nèi)。action-servlet.xml用于配置表現(xiàn)層上下文,其詳細(xì)配置信息如下:

    <?xml version="1.0" encoding="gb2312"?>

    <!-- 指定Spring配置文件的根元素,以及對(duì)應(yīng)的Schame信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 每個(gè)request請(qǐng)求產(chǎn)生一個(gè)新實(shí)例,將所有該請(qǐng)求的作用域配置成request -->

    ??? <bean name="/login" class="lee.LoginAction" scope="request">

    ??????? <property name="vb" ref="vb"/>

    ??? </bean>

    </beans>

    因?yàn)槊看握?qǐng)求都應(yīng)該啟動(dòng)新的Action處理用戶請(qǐng)求,因此,應(yīng)將Action的作用域配置成Request。

    注意:ActionServlet轉(zhuǎn)發(fā)請(qǐng)求時(shí),是根據(jù)Bean的name屬性,而不是id屬性。因此,此處確定的name屬性與Struts的action屬性相同。

    applicationContext.xml只有一個(gè)bean配置,即vb bean。其詳細(xì)配置如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring 配置文件的根元素,以及對(duì)應(yīng)的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 配置ValidBean實(shí)例 -->

    ??? <bean id="vb" class="lee.ValidBeanImpl"/>

    </beans>

    ValidBeanImpl是一個(gè)業(yè)務(wù)邏輯bean,本示例程序中僅作簡(jiǎn)單的判斷,ValidBeanImpl的源代碼如下:

    //面向接口編程,實(shí)現(xiàn)ValidBean接口

    public class ValidBeanImpl implements ValidBean

    {

    ??? //根據(jù)輸入的用戶名和密碼判斷是否有效

    ??? public boolean valid(String username,String pass)

    ??? {

    ??????? //有效,返回true

    ??????? if (username.equals("scott") && pass.equals("tiger"))

    ??????? {

    ??????????? return true;

    ??????? }

    ??????? return false;

    ??? }

    }

    注意:上面的業(yè)務(wù)邏輯組件非常簡(jiǎn)單,它只是一個(gè)示意。如果是真實(shí)的應(yīng)用,業(yè)務(wù)邏輯組件應(yīng)該通過(guò)DAO組件來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯方法。

    應(yīng)用的業(yè)務(wù)邏輯控制器,Action則負(fù)責(zé)調(diào)用業(yè)務(wù)邏輯組件的方法,并根據(jù)業(yè)務(wù)邏輯組件方法的返回值,確定如何響應(yīng)用戶請(qǐng)求。下面是該示例應(yīng)用控制器的代碼:

    //業(yè)務(wù)控制器繼承Action

    public class LoginAction extends Action

    {

    ??? //action控制器將調(diào)用的業(yè)務(wù)邏輯組件

    ??? private ValidBean vb;

    ??? //依賴注入業(yè)務(wù)邏輯組件的setter方法

    ??? public void setVb(ValidBean vb)

    ??? {

    ??????? this.vb = vb;

    ??? }

    ??? //必須重寫(xiě)該核心方法,該方法actionForm將表單的請(qǐng)求參數(shù)封裝成值對(duì)象

    ??? public ActionForward execute(ActionMapping mapping, ActionForm form,

    ?????? HttpServletRequest request, HttpServletResponse response)throws
    ?????? Exception

    ??? {

    ??????? //form由ActionServlet轉(zhuǎn)發(fā)請(qǐng)求時(shí)創(chuàng)建,封裝了所有的請(qǐng)求參數(shù)

    ??????? LoginForm loginForm = (LoginForm)form;

    ??????? //獲取username請(qǐng)求參數(shù)

    ??????? String username = loginForm.getUsername();

    ??????? //獲取pass請(qǐng)求參數(shù)

    ??????? String pass = loginForm.getPass();

    ??????? //下面為服務(wù)器端的數(shù)據(jù)校驗(yàn)

    ??????? String errMsg = "";

    ??????? //判斷用戶名不能為空

    ??????? if (username == null || username.equals(""))

    ??????? {

    ??????????? errMsg += "您的用戶名丟失或沒(méi)有輸入,請(qǐng)重新輸入";

    ??????? }

    ??????? //判斷密碼不能為空

    ??????? else if(pass == null || pass.equals(""))

    ??????? {

    ??????????? errMsg += "您的密碼丟失或沒(méi)有輸入,請(qǐng)重新輸入";

    ??????? }

    ??????? //如果用戶名和密碼不為空,才調(diào)用業(yè)務(wù)邏輯組件

    ??????? else

    ??????? {

    ??????????? //vb是業(yè)務(wù)邏輯組件,由容器注入

    ??????????? if (vb.valid(username,pass))

    ??????????? {

    ??????????????? return mapping.findForward("welcome");

    ??????????? }

    ??????????? else

    ??????????? {

    ??????????????? errMsg = "您的用戶名和密碼不匹配";

    ??????????? }

    ??????? }

    ??????? //判斷是否生成了錯(cuò)誤信息

    ??????? if (errMsg != null && !errMsg.equals(""))

    ??????? {

    ??????????? //如果有錯(cuò)誤信息,將錯(cuò)誤信息保存在request里,并跳轉(zhuǎn)到input對(duì)應(yīng)的
    ??????????? forward對(duì)象

    ??????????? request.setAttribute("err" , errMsg);

    ??????????? return mapping.findForward("input");

    ??????? }

    ??????? else

    ??????? {

    ??????????? //如果沒(méi)有錯(cuò)誤信息,跳轉(zhuǎn)到welcome對(duì)應(yīng)的forward對(duì)象

    ??????????? return mapping.findForward("welcome");

    ??????? }

    ??? }

    }

    在本應(yīng)用中,使用了Struts的客戶端數(shù)據(jù)校驗(yàn),讓Action繼承ValidatorActionForm即可。ActionForm的代碼非常簡(jiǎn)單,此處不再贅述。

    為了完成數(shù)據(jù)校驗(yàn),還應(yīng)該編寫(xiě)數(shù)據(jù)校驗(yàn)規(guī)則文件。在struts-config.xml文件的尾部,另有一個(gè)plug-in用來(lái)加載校驗(yàn)文件,其中validator-rules.xml文件位于struts壓縮包的lib下,直接復(fù)制過(guò)來(lái)即可使用,而validator.xml必須自己編寫(xiě),validator.xml文件如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 驗(yàn)證規(guī)則文件的文件頭,包括DTD等信息 -->

    <!DOCTYPE form-validation PUBLIC

    ????????? "-//Apache Software Foundation//DTD Commons Validator Rules
    ????????? Configuration 1.1.3//EN"

    ????????? "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">

    <!-- 驗(yàn)證文件的根元素 -->

    <form-validation>

    ??? <!-- 所有需要驗(yàn)證的form都放在formset里 -->

    <formset>

    ??? <!-- 需要驗(yàn)證的form名,該名與struts里配置的名相同 -->

    ??? <form name="loginForm">

    ??????? <!-- 指定該form的username域必須滿足的規(guī)則:必填、模式匹配 -->

    ??????? <field property="username" depends="required,mask">

    ??????? ??? <arg key="loginForm.username" position="0"/>

    ??????????? <var>

    ??????????????? <!-- 確定匹配模式的正則表達(dá)式 -->

    ??????????? <var-name>mask</var-name>

    ??????????? <var-value>^[a-zA-Z]+$</var-value>

    ??????????? </var>

    ??????? </field>

    ??????? <!-- 指定該form的pass域必須滿足的規(guī)則:必填 -->

    ??????? <field property="pass" depends="required">

    ??????????? <msg name="required" key="pass.required"/>

    ??????? ??? <arg key="loginForm.pass" position="0"/>

    ??????? </field>

    ??? </form>

    </formset>

    </form-validation>

    上面示例程序的結(jié)構(gòu)非常清晰:表現(xiàn)層組件(Action)配置在action-servlet.xml文件中,而業(yè)務(wù)邏輯層組件(vb)配置在applicationContext.xml文件中,如果應(yīng)用中有DAO組件,將DAO組件配置在dao-context.xml文件中。將3個(gè)文件放在plug-in元素里一起加載。

    文本框:圖6.3  DelegatingRequestProcessor整合策略的登錄失敗效果DelegatingRequestProcessor會(huì)將請(qǐng)求轉(zhuǎn)發(fā)到Action,該Action已經(jīng)處于IoC容器管理之下,因此,可以方便地訪問(wèn)容器中的其他Bean。

    通過(guò)配置文件可以看出,Action根本無(wú)須type屬性,即struts-config.xml中Action根本沒(méi)有實(shí)例化過(guò),DelegatingRequestProcessor將請(qǐng)求轉(zhuǎn)發(fā)給Spring容器中的同名Bean。這種轉(zhuǎn)發(fā)的時(shí)機(jī)非常早,避免了創(chuàng)建struts-config.xml配置文件中的Action,因而性能非常好。

    圖6.3是采用這種整合策略的執(zhí)行效果。

    6.4.4 使用DelegatingActionProxy

    使用DelegatingRequestProcessor簡(jiǎn)單方便,但有一個(gè)缺點(diǎn),RequestProcessor是Struts的一個(gè)擴(kuò)展點(diǎn),也許應(yīng)用程序本身就需要擴(kuò)展RequestProcessor,而DelegatingRequest- Processor已經(jīng)使用了這個(gè)擴(kuò)展點(diǎn)。

    為了重新利用Struts的RequestProcessor這個(gè)擴(kuò)展點(diǎn),有兩個(gè)做法:

    ?? ● 應(yīng)用程序的RequestProcessor不再繼承Struts的RequestProcessor,改為繼承DelegatingRequestProcessor。

    ?? ● 使用DelegatingActionProxy。

    前者常常有一些未知的風(fēng)險(xiǎn),而后者是Spring推薦的整合策略。使用Delegating- ActionProxy與DelegatingRequestProcessor的目的都只有一個(gè),將請(qǐng)求轉(zhuǎn)發(fā)給Spring管理的Bean。

    DelegatingRequestProcessor直接替換了原有的RequestProcessor,在請(qǐng)求轉(zhuǎn)發(fā)給action之前,轉(zhuǎn)發(fā)給Spring管理的Bean;而DelegatingActionProxy則被配置成Struts的Action,即所有的請(qǐng)求先被ActionServlet截獲,請(qǐng)求被轉(zhuǎn)發(fā)到對(duì)應(yīng)的Action,而action的實(shí)現(xiàn)類全都是DelegatingActionProxy,DelegatingActionProxy再將請(qǐng)求轉(zhuǎn)發(fā)給Spring容器的Action。

    可以看出,使用DelegatingActionProxy比使用DelegatingRequestProcessor要晚一步轉(zhuǎn)發(fā)到Spring的context。但通過(guò)這種方式可以避免占用擴(kuò)展點(diǎn)。

    與使用DelegatingRequestProcessor相比,使用DelegatingActionProxy僅需要去掉controller配置元素,并將所有的action實(shí)現(xiàn)類改為DelegatingActionProxy即可。詳細(xì)的配置文件如下:

    <!-- XML文件的版本和編碼集 -->

    <?xml version="1.0" encoding="gb2312"?>

    <!-- struts配置文件的文件頭,包括DTD等信息 -->

    <!DOCTYPE struts-config PUBLIC

    ????????? "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"

    ????????? "http://struts.apache.org/dtds/struts-config_1_2.dtd">

    <!-- struts配置文件的根元素 -->

    <struts-config>

    ??? <!-- 配置formbean,所有的formbean都放在form-beans元素里定義 -->

    ??? <form-beans>

    ??????? <!-- 定義了一個(gè)formbean,確定formbean名和實(shí)現(xiàn)類 -->

    ??????? <form-bean name="loginForm" type="lee.LoginForm"/>

    ??? </form-beans>

    ??? <!-- 定義action部分,所有的action都放在action-mapping元素里定義 -->

    ??? <action-mappings>

    ??????? <!-- 這里只定義了一個(gè)action。必須配置action的type元素為
    ??? ??? DelegatingActionProxy -->

    ??????? <action path="/login" type="org.springframework.web.struts.
    ??????? DelegatingActionProxy"

    ??????????? name="loginForm" scope="request" validate="true" input=
    ??????????? "/login.jsp" >

    ??????????? <!-- 定義action內(nèi)的兩個(gè)局部forward元素 -->

    ??????????? <forward name="input" path="/login.jsp"/>

    ??????????? <forward name="welcome" path="/welcome.html"/>

    ??????? </action>

    ??? </action-mappings>

    ??? <!-- 加載國(guó)際化的資源包 -->

    ??? <message-resources parameter="mess"/>

    ??? <!-- 裝載驗(yàn)證的資源文件 -->

    ??? <plug-in className="org.apache.struts.validator.ValidatorPlugIn">

    ??????? <set-property property="pathnames" value="/WEB-INF/validator-
    ??????? rules.xml,/WEB-INF/validation.xml" />

    ??????? <set-property property="stopOnFirstError" value="true"/>

    ??? </plug-in>

    ??? <!-- 裝載Spring配置文件,隨應(yīng)用啟動(dòng)創(chuàng)建ApplicationContext實(shí)例 -->

    ??? <plug-in className="org.springframework.web.struts. ContextLoaderPlugIn">
    ??????? <set-property property="contextConfigLocation"

    ??????????? value="/WEB-INF/applicationContext.xml,

    ?????????????????? /WEB-INF/action-servlet.xml"/>

    ??? </plug-in>

    </struts-config>

    DelegatingActionProxy接收ActionServlet轉(zhuǎn)發(fā)過(guò)來(lái)的請(qǐng)求,然后轉(zhuǎn)發(fā)給Application- Context管理的Bean,這是典型的鏈?zhǔn)教幚怼?/p>

    通過(guò)配置文件可以看出,struts-config.xml文件中配置了大量DelegatingActionProxy實(shí)例,Spring容器中也配置了同名的Action。即Struts的業(yè)務(wù)控制器分成了兩個(gè)部分:第一個(gè)部分是Spring的DelegatingActionProxy,這個(gè)部分沒(méi)有實(shí)際意義,僅僅完成轉(zhuǎn)發(fā);第二個(gè)部分是用戶的Action實(shí)現(xiàn)類,該實(shí)現(xiàn)類負(fù)責(zé)真實(shí)的處理。

    這種策略的性能比前一種策略的效果要差一些,因?yàn)樾枰鄤?chuàng)建一個(gè)Delegating- ActionProxy實(shí)例。而且,J2EE應(yīng)用中Action非常多,這將導(dǎo)致大量創(chuàng)建DelegatingActionProxy實(shí)例,使用一次之后,等待垃圾回收機(jī)制回收——這對(duì)性能的影響不可避免。

    圖6.4是DelegatingActionProxy的執(zhí)行效果。

    文本框:圖6.4  DelegatingActionProxy整合策略的登錄成功效果注意:使用DelegatingActionProxy的整合策略,可避免占用Struts的RequestProcessor擴(kuò)展點(diǎn),但降低了整合性能。

    6.4.5 使用ActionSupport代替Action

    前面已經(jīng)介紹了,Spring與Struts的整合還有一種策略,讓Struts的Action顯式獲取Spring容器中的Bean。在這種策略下,Struts的Action不接受IoC容器管理,Action的代碼與Spring API部分耦合,造成代碼污染。這種策略也有其好處:代碼的可讀性非常強(qiáng),Action的代碼中顯式調(diào)用業(yè)務(wù)邏輯組件,而無(wú)須等待容器注入。

    Action中訪問(wèn)ApplicationContext有兩種方法:

    ?? ● 利用WebApplicationContextUtils工具類。

    ?? ● 利用ActionSupport支持類。

    通過(guò)WebApplicationContextUtils,可以顯式獲得Spring容器的引用(請(qǐng)參閱6.4.1節(jié)的內(nèi)容),而ActionSupport類則提供了一個(gè)更簡(jiǎn)單的方法getWebApplicationContext(),該方法可直接獲取Spring容器的引用。

    所謂ActionSupport類,是指Spring提供了系列擴(kuò)展。Spring擴(kuò)展了Struts的Action,在Struts的Action后加上Support后綴,Spring擴(kuò)展的Action有如下幾個(gè):

    ?? ● ActionSupport。

    ?? ● DispatchActionSupport。

    ?? ● LookupDispatchActionSupport。

    ?? ● MappingDispatchActionSupport。

    下面的示例將展示這種整合策略,在這種整合策略下,Struts的Action改為繼承Spring擴(kuò)展后的Action,下面是應(yīng)用的Action代碼:

    //新的業(yè)務(wù)控制器,繼承Spring的ActionSupport類

    public class LoginAction extends ActionSupport

    {

    ??? //依然將ValidBean作為成員變量

    ??? private ValidBean vb;

    ??? //構(gòu)造器,注意:不可在構(gòu)造器中調(diào)用getWebApplicationContext()方法

    ??? public LoginAction()

    ??? {

    ??? }

    ??? //完成ValidBean的初始化

    ??? public ValidBean getVb()

    ??? {

    ??????? return(ValidBean)getWebApplicationContext().getBean("vb");

    ??? }

    ??? //必須重寫(xiě)該核心方法,該方法actionForm將表單的請(qǐng)求參數(shù)封裝成值對(duì)象

    ??? public ActionForward execute(ActionMapping mapping, ActionForm form,

    ?????? HttpServletRequest request, HttpServletResponse response)throws
    ?????? Exception

    ??? {

    ??????? //form由ActionServlet轉(zhuǎn)發(fā)請(qǐng)求時(shí)創(chuàng)建,封裝了所有的請(qǐng)求參數(shù)

    ??????? LoginForm loginForm = (LoginForm)form;

    ??????? //獲取username請(qǐng)求參數(shù)

    ??????? String username = loginForm.getUsername();

    ??????? //獲取pass請(qǐng)求參數(shù)

    ??????? String pass = loginForm.getPass();

    ??????? //下面為服務(wù)器端的數(shù)據(jù)校驗(yàn)

    ??????? String errMsg = "";

    ??????? //判斷用戶名不能為空

    ??????? if (username == null || username.equals(""))

    ??????? {

    ??????????? errMsg += "您的用戶名丟失或沒(méi)有輸入,請(qǐng)重新輸入";

    ??????? }

    ??????? //判斷密碼不能為空

    ??????? else if(pass == null || pass.equals(""))

    ??????? {

    ??????????? errMsg += "您的密碼丟失或沒(méi)有輸入,請(qǐng)重新輸入";

    ??????? }

    ??????? //如果用戶名和密碼不為空,才調(diào)用業(yè)務(wù)邏輯組件

    ??????? else

    ??????? {

    ??????????? //vb是業(yè)務(wù)邏輯組件,通過(guò)上面的初始化方法獲得

    ??????????? if (getVb().valid(username,pass))

    ??????????? {

    ??????????????? return mapping.findForward("welcome");

    ??????????? }

    ??????????? else

    ??????????? {

    ??????????????? errMsg = "您的用戶名和密碼不匹配";

    ??????????? }

    ??????? }

    ??????? //判斷是否生成了錯(cuò)誤信息

    ??????? if (errMsg != null && !errMsg.equals(""))

    ??????? {

    ??????????? //如果有錯(cuò)誤信息,將錯(cuò)誤信息保存在request里,并跳轉(zhuǎn)到input對(duì)應(yīng)的
    ??????????? //forward對(duì)象

    ??????????? request.setAttribute("err" , errMsg);

    ??????????? return mapping.findForward("input");

    ??????? }

    ??????? else

    ??????? {

    ??????????? //如果沒(méi)有錯(cuò)誤信息,跳轉(zhuǎn)到welcome對(duì)應(yīng)的forward對(duì)象

    ??????????? return mapping.findForward("welcome");

    ??????? }

    ??? }

    }

    在上面的Action代碼中,Action顯式獲取容器中的業(yè)務(wù)邏輯組件,而不是依靠Spring容器的依賴注入。在這種整合策略下,表現(xiàn)層的控制器組件不再接受IoC容器管理。因此,沒(méi)有控制器上下文,應(yīng)將原有的action-servlet.xml文件刪除,并修改plug-in元素,不要加載該文件。還要修改Action配置,將Action配置的type元素修改成實(shí)際的處理類。這????? 種整合策略也有一個(gè)好處:代碼的可讀性更強(qiáng),對(duì)傳統(tǒng)Struts應(yīng)用開(kāi)發(fā)的改變很小,容易使用。

    將該Action部署在struts-config.xml中,Struts將負(fù)責(zé)創(chuàng)建該Action。struts-config.xml文件的源代碼如下:

    <!-- XML文件的版本和編碼集 -->

    <?xml version="1.0" encoding="gb2312"?>

    <!-- Struts配置文件的文件頭,包括DTD等信息 -->

    <!DOCTYPE struts-config PUBLIC

    ????????? "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"

    ????????? "http://struts.apache.org/dtds/struts-config_1_2.dtd">

    <!-- struts配置文件的根元素 -->

    <struts-config>

    ??? <!-- 配置formbean,所有的formbean都放在form-beans元素里定義 -->

    ??? <form-beans>

    ??????? <!-- 定義了一個(gè)formbean,確定formbean名和實(shí)現(xiàn)類 -->

    ??????? <form-bean name="loginForm" type="lee.LoginForm"/>

    ??? </form-beans>

    ??? <!-- 定義action部分,所有的action都放在action-mapping元素里定義 -->

    ??? <action-mappings>

    ??????? <!-- 這里只定義了一個(gè)action。action的類型為ActionSupport的子類 -->

    ??????? <action path="/login" type="type="lee.LoginAction"

    ??????????? name="loginForm" scope="request" validate="true" input=
    ??????????? "/login.jsp" >

    ??????????? <!-- 定義action內(nèi)的兩個(gè)局部forward元素 -->

    ??????????? <forward name="input" path="/login.jsp"/>

    ??????????? <forward name="welcome" path="/welcome.html"/>

    ??????? </action>

    ??? </action-mappings>

    ??? <!-- 加載國(guó)際化的資源包 -->

    ??? <message-resources parameter="mess"/>

    ??? <!-- 裝載驗(yàn)證的資源文件 -->

    ??? <plug-in className="org.apache.struts.validator.ValidatorPlugIn">

    ??????? <set-property property="pathnames" value="/WEB-INF/validator-
    ??????? rules.xml,/WEB-INF/validation.xml" />

    ??????? <set-property property="stopOnFirstError" value="true"/>

    ??? </plug-in>

    </struts-config>

    此時(shí),Spring無(wú)須使用配置Action的配置文件,這種配置方式非常簡(jiǎn)單。只需要業(yè)務(wù)邏輯組件的配置文件,業(yè)務(wù)邏輯組件的配置文件與前面的示例沒(méi)有任何改變。

    該配置文件中的業(yè)務(wù)邏輯組件由Spring容器負(fù)責(zé)實(shí)現(xiàn),而ActionSupport能夠先定位Spring容器,然后獲得容器的業(yè)務(wù)邏輯組件。

    這種整合策略的執(zhí)行效果與前面兩種整合策略的執(zhí)行效果完全相同。從代碼中分析可見(jiàn),在這種整合策略下,業(yè)務(wù)控制器再次退回到Struts起初的設(shè)計(jì)。僅由strutsconfig.xml中Action充當(dāng),從而避免了像DelegatingActionProxy整合策略的性能低下,因?yàn)榭梢灾恍枰獎(jiǎng)?chuàng)建實(shí)際的Action實(shí)例。

    注意:在這種整合策略下,Struts開(kāi)發(fā)者的改變最小,最接近傳統(tǒng)Struts應(yīng)用開(kāi)發(fā)者的習(xí)慣。但這種整合策略會(huì)造成代碼污染,因?yàn)锳ction類必須繼承Spring的ActionSupport類。

    posted @ 2009-07-19 10:23 jadmin 閱讀(78) | 評(píng)論 (0)編輯 收藏

    6.4 Spring整合Struts

    雖然Spring也提供了自己的MVC組件,但一來(lái)Spring的MVC組件過(guò)于繁瑣,二???? 來(lái)Struts的擁護(hù)者實(shí)在太多。因此,很多項(xiàng)目都會(huì)選擇使用Spring整合Struts框架。而且Spring確實(shí)可以無(wú)縫整合Struts框架,二者結(jié)合成一個(gè)更實(shí)際的J2EE開(kāi)發(fā)平臺(tái)。

    6.4.1 利用Struts的PlugIn來(lái)啟動(dòng)Spring容器

    使用Spring的Web應(yīng)用時(shí),不用手動(dòng)創(chuàng)建Spring容器,而是通過(guò)配置文件聲明式地創(chuàng)建Spring容器。因此,在Web應(yīng)用中創(chuàng)建Spring容器有如下兩個(gè)方式:

    ?? ● 直接在web.xml文件中配置創(chuàng)建Spring容器。

    ?? ● 利用第三方MVC框架的擴(kuò)展點(diǎn),創(chuàng)建Spring容器。

    其實(shí)第一種創(chuàng)建Spring容器的方式更加常見(jiàn)。為了讓Spring容器隨Web應(yīng)用的啟動(dòng)而自動(dòng)啟動(dòng),有如下兩個(gè)方法:

    ?? ● 利用ServletContextListener實(shí)現(xiàn)。

    ?? ● 采用load-on-startup Servlet實(shí)現(xiàn)。

    Spring提供ServletContextListener的一個(gè)實(shí)現(xiàn)類ContextLoaderListener,該類可以作為L(zhǎng)istener使用,會(huì)在創(chuàng)建時(shí)自動(dòng)查找WEB-INF/下的applicationContext.xml文件,因此,如果只有一個(gè)配置文件,并且文件名為applicationContext.xml,只需在web.xml文件中增加如下配置片段即可:

    <listener>

    ?? <listener-class>org.springframework.web.context.
    ??? ContextLoaderListener</listener-class>

    </listener>

    如果有多個(gè)配置文件需要載入,則考慮使用<context-param>元素來(lái)確定配置文件的文件名。ContextLoaderListener加載時(shí),會(huì)查找名為contextConfigLocation的參數(shù)。因此,配置context-param時(shí),參數(shù)名字應(yīng)該是contextConfigLocation。

    帶多個(gè)配置文件的web.xml文件如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Web配置文件的根元素,以及相應(yīng)的Schema信息 -->

    <web-app xmlns="http://java.sun.com/xml/ns/j2ee"

    ??? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ??? xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee ??
    ??? http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

    ??? version="2.4">

    ??? <!-- 確定多個(gè)配置文件 -->

    ??? <context-param>

    ??????? <!-- 參數(shù)名為contextConfigLocation -->

    ??????? <param-name>contextConfigLocation</param-name>

    ??????? <!-- 多個(gè)配置文件之間以“,”隔開(kāi) -->

    ??????? <param-value>/WEB-INF/daoContext.xml,/WEB-INF/
    ??????? applicationContext.xml</param-value>

    ??? </context-param>

    ??? <!-- 采用listener創(chuàng)建ApplicationContext實(shí)例 -->

    ??? <listener>

    ??????? <listener-class>org.springframework.web.context.
    ??????? ContextLoaderListener</listener-class>

    ??? </listener>

    </web-app>

    如果沒(méi)有通過(guò)contextConfigLocation指定配置文件,Spring會(huì)自動(dòng)查找application- Context.xml配置文件;如果有contextConfigLocation,則利用該參數(shù)確定的配置文件。如果無(wú)法找到合適的配置文件,Spring將無(wú)法正常初始化。

    Spring根據(jù)bean定義創(chuàng)建WebApplicationContext對(duì)象,并將其保存在web應(yīng)用的ServletContext中。大部分情況下,應(yīng)用中的Bean無(wú)須感受到ApplicationContext的存在,只要利用ApplicationContext的IoC即可。

    如果需要在應(yīng)用中獲取ApplicationContext實(shí)例,可以通過(guò)如下代碼獲取:

    //獲取當(dāng)前Web應(yīng)用的Spring容器

    WebApplicationContext ctx =

    ??? WebApplicationContextUtils.getWebApplicationContext(servletContext);

    除此之外,Spring提供了一個(gè)特殊的Servlet類ContextLoaderServlet。該Servlet在啟動(dòng)時(shí),會(huì)自動(dòng)查找WEB-INF/下的applicationContext.xml文件。

    當(dāng)然,為了讓ContextLoaderServlet隨應(yīng)用的啟動(dòng)而啟動(dòng),應(yīng)將此Servlet配置成load-on-startup的Servlet,load-on-startup的值小一點(diǎn)比較合適,這樣可以保證Application- Context更快的初始化。

    如果只有一個(gè)配置文件,并且文件名為applicationContext.xml,在web.xml文件中增加如下一段即可:

    <servlet>

    ??? <servlet-name>context</servlet-name>

    ??? <servlet-class>org.springframework.web.context.ContextLoaderServlet
    ??? </servlet-class>

    ??? <load-on-startup>1</load-on-startup>

    </servlet>

    該Servlet用于提供“后臺(tái)”服務(wù),主要用于創(chuàng)建Spring容器,無(wú)須響應(yīng)客戶請(qǐng)求,因此無(wú)須配置servlet-mapping。

    如果有多個(gè)配置文件,一樣使用<context-param>元素來(lái)確定多個(gè)配置文件。

    事實(shí)上,不管是ContextLoaderServlet,還是ContextLoaderListener,都依賴于ContextLoader創(chuàng)建ApplicationContext實(shí)例。

    在ContextLoader代碼的第240行,有如下代碼:

    String configLocation = servletContext.getInitParameter
    (CONFIG_LOCATION_PARAM);

    if (configLocation != null) {

    ??? wac.setConfigLocations(StringUtils.tokenizeToStringArray
    ??? (configLocation,

    ??? ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));

    }

    其中,CONFIG_LOCATION_PARAM是該類的常量,其值為contextConfigLocation。可以看出,ContextLoader首先檢查servletContext中是否有contextConfigLocation的參數(shù),如果有該參數(shù),則加載該參數(shù)指定的配置文件。

    ContextLoaderServlet與ContextLoaderListener底層都依賴于ContextLoader。因此,二者的效果幾乎沒(méi)有區(qū)別。之間的區(qū)別不是它們本身引起的,而是由于Servlet規(guī)范,Listener比Servlet優(yōu)先加載。因此,采用ContextLoaderListener創(chuàng)建ApplicationContext的時(shí)機(jī)更早。

    當(dāng)然,也可以通過(guò)ServletContext的getAttribute方法獲取ApplicationContext。但使用WebApplicationContextUtils類更便捷,因?yàn)闊o(wú)須記住ApplicationContext的屬性名。即使ServletContext的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRI- BUTE屬性沒(méi)有對(duì)應(yīng)對(duì)象,WebApplicationContextUtils的getWebApplicationContext()方法將會(huì)返回空,而不會(huì)引起異常。

    到底需要使用Listener,還是使用load-on-startup Servlet來(lái)創(chuàng)建Spring容器呢?通常推薦使用Listener來(lái)創(chuàng)建Spring容器。但Listerner是Servlet 2.3以上才支持的標(biāo)準(zhǔn),因此,必須Web容器支持Listener才可使用Listerner。

    注意:使用Listener創(chuàng)建Spring容器之前,應(yīng)先評(píng)估Web容器是否支持Listener標(biāo)準(zhǔn)。

    還有一種情況,利用第三方MVC框架的擴(kuò)展點(diǎn)來(lái)創(chuàng)建Spring容器,比如Struts。在第2章介紹Strust框架時(shí),知道Struts有一個(gè)擴(kuò)展點(diǎn)PlugIn。

    實(shí)際上,Spring正是利用了PlugIn這個(gè)擴(kuò)展點(diǎn),從而提供與Struts的整合。Spring提供了PlugIn接口的實(shí)現(xiàn)類org.springframework.web.struts.ContextLoaderPlugIn。這個(gè)實(shí)現(xiàn)類可作為Struts的PlugIn配置,Struts框架啟動(dòng)時(shí),將自動(dòng)創(chuàng)建Spring容器。

    為了利用Struts的PlugIn創(chuàng)建Spring容器,只需在Struts配置文件中增加如下片段?? 即可:

    <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">

    <set-property property="contextConfigLocation"

    ????? value="/WEB-INF/action-servlet.xml,/WEB-INF/applicationContext.
    ????? xml"/>

    </plug-in>

    其中,指定contextConfigLocation屬性值時(shí),即可以指定一個(gè)Spring配置文件的位置,可以指定多個(gè)Spring配置文件的位置。

    6.4.2 MVC框架與Spring整合的思考

    對(duì)于一個(gè)基于B/S架構(gòu)的J2EE應(yīng)用而言,用戶請(qǐng)求總是向MVC框架的控制器請(qǐng)求,而當(dāng)控制器攔截到用戶請(qǐng)求后,必須調(diào)用業(yè)務(wù)邏輯組件來(lái)處理用戶請(qǐng)求。此時(shí)有一個(gè)問(wèn)題,控制器應(yīng)該如何獲得業(yè)務(wù)邏輯組件?

    最容易想到的策略是,直接通過(guò)new關(guān)鍵字創(chuàng)建業(yè)務(wù)邏輯組件,然后調(diào)用業(yè)務(wù)邏輯組件的方法,根據(jù)業(yè)務(wù)邏輯方法的返回值確定結(jié)果。

    實(shí)際的應(yīng)用中,很少見(jiàn)到采用上面的訪問(wèn)策略,因?yàn)檫@是一種非常差的策略。不這樣做至少有如下3個(gè)原因:

    ?? ● 控制器直接創(chuàng)建業(yè)務(wù)邏輯組件,導(dǎo)致控制器和業(yè)務(wù)邏輯組件的耦合降低到代碼層次,不利于高層次解耦。

    ?? ● 控制器不應(yīng)該負(fù)責(zé)業(yè)務(wù)邏輯組件的創(chuàng)建,控制器只是業(yè)務(wù)邏輯組件的使用者。無(wú)須關(guān)心業(yè)務(wù)邏輯組件的實(shí)現(xiàn)。

    ?? ● 每次創(chuàng)建新的業(yè)務(wù)邏輯組件將導(dǎo)致性能下降。

    答案是采用工廠模式或服務(wù)定位器。采用服務(wù)定位器的模式,是遠(yuǎn)程訪問(wèn)的場(chǎng)景。在這種場(chǎng)景下,業(yè)務(wù)邏輯組件已經(jīng)在某個(gè)容器中運(yùn)行,并對(duì)外提供某種服務(wù)。控制器無(wú)須理會(huì)該業(yè)務(wù)邏輯組件的創(chuàng)建,直接調(diào)用即可,但在調(diào)用之前,必須先找到該服務(wù)——這就是服務(wù)定位器的概念。經(jīng)典J2EE應(yīng)用就是這種結(jié)構(gòu)的應(yīng)用。

    對(duì)于輕量級(jí)的J2EE應(yīng)用,工廠模式則是更實(shí)際的策略。因?yàn)檩p量級(jí)的J2EE應(yīng)用里,業(yè)務(wù)邏輯組件不是EJB,通常就是一個(gè)POJO,業(yè)務(wù)邏輯組件的生成通常由工廠負(fù)責(zé),而且工廠可以保證該組件的實(shí)例只需一個(gè)就夠了,可以避免重復(fù)實(shí)例化造成的系統(tǒng)開(kāi)銷。

    如圖6.2就是采用工廠模式的順序圖。

    圖6.2 工廠模式順序圖

    采用工廠模式,將控制器與業(yè)務(wù)邏輯組件的實(shí)現(xiàn)分離,從而提供更好的解耦。

    在采用工廠模式的訪問(wèn)策略中,所有的業(yè)務(wù)邏輯組件的創(chuàng)建由工廠負(fù)責(zé),業(yè)務(wù)邏輯組件的運(yùn)行也由工廠負(fù)責(zé)。而控制器只需定位工廠實(shí)例即可。

    如果系統(tǒng)采用Spring框架,則Spring成為最大的工廠。Spring負(fù)責(zé)業(yè)務(wù)邏輯組件的創(chuàng)建和生成,并可管理業(yè)務(wù)邏輯組件的生命周期。可以如此理解,Spring是一個(gè)性能非常優(yōu)秀的工廠,可以生產(chǎn)出所有的實(shí)例,從業(yè)務(wù)邏輯組件,到持久層組件,甚至控制器。

    現(xiàn)在的問(wèn)題是,控制器如何訪問(wèn)到Spring容器中的業(yè)務(wù)邏輯組件?為了讓Action訪 問(wèn)Spring的業(yè)務(wù)邏輯組件,有兩種策略:

    ?? ● Spring管理控制器,并利用依賴注入為控制器注入業(yè)務(wù)邏輯組件。

    ?? ● 控制器顯式定位Spring工廠,也就是Spring的容器ApplicationContext實(shí)例,并從工廠中獲取業(yè)務(wù)邏輯組件實(shí)例的引用。

    第一種策略,充分利用Spring的IoC特性,是最優(yōu)秀的解耦策略。但不可避免帶來(lái)一些不足之處,歸納起來(lái)主要有如下不足之處:

    ?? ● Spring管理Action,必須將所有的Action配置在Spring容器中,而struts-config.xml文件中的配置也不會(huì)減少,導(dǎo)致配置文件大量增加。

    ?? ● Action的業(yè)務(wù)邏輯組件接收容器注入,將導(dǎo)致代碼的可讀性降低。

    總體而言,這種整合策略是利大于弊。

    第二種策略,與前面介紹的工廠模式并沒(méi)有太大的不同。區(qū)別是Spring容器充當(dāng)了業(yè)務(wù)邏輯組件的工廠。控制器負(fù)責(zé)定位Spring容器,通常Spring容器訪問(wèn)容器中的業(yè)務(wù)邏輯組件。這種策略是一種折衷,降低了解耦,但提高了程序的可讀性。

    Spring完全支持這兩種策略,既可以讓Spring容器管理控制器,也可以讓控制器顯式定位Spring容器中的業(yè)務(wù)邏輯組件。

    6.4.3 使用DelegatingRequestProcessor

    這里介紹的是第一種整合策略:讓Spring管理Struts的Action。那么同樣有一個(gè)問(wèn)題,讓Spring管理Struts的Action時(shí),客戶端的HTTP 請(qǐng)求如何轉(zhuǎn)向Spring容器中的Action?

    當(dāng)使用Struts作為MVC框架時(shí),客戶端的HTTP請(qǐng)求都是直接向ActionServlet請(qǐng)求的,因此關(guān)鍵就是讓ActionServlet將請(qǐng)求轉(zhuǎn)發(fā)給Spring容器中的Action。這很明顯可以利用Spring的另一個(gè)擴(kuò)展點(diǎn):通過(guò)擴(kuò)展RequestProcessor完成,使用擴(kuò)展的RequestProcessor替換Struts的RequestProcessor。

    Spring完成了這種擴(kuò)展,Spring提供的DelegatingRequestProcessor繼承Request- Processor。為了讓Struts使用DelegatingRequestProcessor,還需要在struts-config.xml文件中增加如下一行:

    //使用spring的RequestProcessor替換struts原有的RequestProcessor

    <controller processorClass="org.springframework.web.struts.
    DelegatingRequestProcessor"/>

    完成這個(gè)設(shè)置后,Struts會(huì)將截獲到的用戶請(qǐng)求轉(zhuǎn)發(fā)到Spring context下的bean,根據(jù)bean的name屬性來(lái)匹配。而Struts中的action配置則無(wú)須配置class屬性,即使配置了class屬性也沒(méi)有任何用處,即下面兩行配置是完全一樣的:

    //配置struts action時(shí),指定了實(shí)現(xiàn)類

    <action path="/user" type="lee.UserAction"/>

    //配置struts action時(shí),沒(méi)有指定實(shí)現(xiàn)類

    <action path="/user"/>

    下面的示例程序在上一個(gè)示例程序的基礎(chǔ)上稍作修改,增加了客戶端驗(yàn)證和程序國(guó)際化部分。也調(diào)用了Spring的業(yè)務(wù)bean來(lái)驗(yàn)證登錄。先看修改后的struts-config.xml文件:

    <!-- XML文件版本,編碼集 -->

    <?xml version="1.0" encoding="gb2312"?>

    <!-- Struts配置文件的文件頭,包括DTD等信息 -->

    <!DOCTYPE struts-config PUBLIC

    ????????? "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"

    ????????? "http://struts.apache.org/dtds/struts-config_1_2.dtd">

    <!-- struts配置文件的根元素 -->

    <struts-config>

    ??? <!-- 配置formbean,所有的formbean都放在form-beans元素里定義 -->

    ??? <form-beans>

    ??????? <!-- 定義了一個(gè)formbean,確定formbean名和實(shí)現(xiàn)類 -->

    ??????? <form-bean name="loginForm" type="lee.LoginForm"/>

    ??? </form-beans>

    ??? <!-- 定義action部分,所有的action都放在action-mapping元素里定義 -->

    ??? <action-mappings>

    ??????? <!-- 這里只定義了一個(gè)action。而且沒(méi)有指定該action的type元素 -->

    ??????? <action path="/login" name="loginForm"

    ??????????? scope="request" validate="true" input="/login.jsp" >

    ??????????? <!-- 定義action內(nèi)的兩個(gè)局部forward元素 -->

    ??????????? <forward name="input" path="/login.jsp"/>

    ??????????? <forward name="welcome" path="/welcome.html"/>

    ??????? </action>

    ??? </action-mappings>

    ??? <!-- 使用DelegatingRequestProcessor替換RequestProcessor -->

    ??? <controller processorClass="org.springframework.web.struts.
    ??? DelegatingRequestProcessor"/>

    ??? <!-- 加載國(guó)際化的資源包 -->

    ??? <message-resources parameter="mess"/>

    ??? <!-- 裝載驗(yàn)證的資源文件 -->

    ??? <plug-in className="org.apache.struts.validator.ValidatorPlugIn">

    ??????? <set-property property="pathnames" value="/WEB-INF/validator-
    ??????? rules.xml,/WEB-INF/validation.xml" />

    ??????? <set-property property="stopOnFirstError" value="true"/>

    ??? </plug-in>

    ??? <!-- 裝載Spring配置文件,隨應(yīng)用的啟動(dòng)創(chuàng)建ApplicationContext實(shí)例 -->

    ??? <plug-in className="org.springframework.web.struts.
    ??? ContextLoaderPlugIn">

    ??????? <set-property property="contextConfigLocation"

    ??????????? value="/WEB-INF/applicationContext.xml,

    ?????????????????? /WEB-INF/action-servlet.xml"/>

    ??? </plug-in>

    </struts-config>

    修改后的struts-config.xml文件,增加加載國(guó)際化資源文件。配置Struts的action不需要class屬性,完成了ApplicationContext的創(chuàng)建。

    然后考慮web.xml文件的配置,在web.xml文件中必須配置Struts框架的加載。除此之外,因?yàn)槭褂昧薙pring管理Struts的Action,而Action是隨HTTP請(qǐng)求啟動(dòng)的,因此,應(yīng)將Action的作用域配置成Request,為了使用Request作用域,必須在web.xml文件中增加適當(dāng)?shù)呐渲谩?/p>

    下面是web.xml文件的代碼:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Web配置文件的根元素,以及對(duì)應(yīng)的Schema信息 -->

    <web-app xmlns="http://java.sun.com/xml/ns/j2ee"

    ??? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ??? xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
    ??? http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

    ??? version="2.4">

    ??? <!-- 定義一個(gè)Filter,該Filter是使用Request作用域的基礎(chǔ) -->

    ??? <filter>

    ??????? <filter-name>requestContextFilter</filter-name>

    ??????? <filter-class>org.springframework.web.filter.
    ??????? RequestContextFilter </filter-class>

    ??? </filter>

    ??? <!-- 定義filter-mapping,讓上面的Filter過(guò)濾所有的用戶請(qǐng)求 -->

    ??? <filter-mapping>

    ??????? <filter-name>requestContextFilter</filter-name>

    ??????? <url-pattern>/*</url-pattern>

    ??? </filter-mapping>

    ??? <!-- 定義Struts的核心Servlet -->

    ??? <servlet>

    ??????? <servlet-name>action</servlet-name>

    ??????? <servlet-class>org.apache.struts.action.ActionServlet
    ??????? </servlet-class>

    ??????? <load-on-startup>2</load-on-startup>

    ??? </servlet>

    ??? <!-- 定義Struts的核心Servlet攔截所有*.do請(qǐng)求 -->

    ??? <servlet-mapping>

    ??????? <servlet-name>action</servlet-name>

    ??????? <url-pattern>*.do</url-pattern>

    ??? </servlet-mapping>

    ??? <!-- 關(guān)于Struts標(biāo)簽庫(kù)的配置 -->

    ??? <jsp-config>

    ??????? <!-- 配置bean標(biāo)簽 -->

    ??????? <taglib>

    ??????????? <taglib-uri>/tags/struts-bean</taglib-uri>

    ??????????? <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>

    ??????? </taglib>

    ??????? <!-- 配置html標(biāo)簽 -->

    ??????? <taglib>

    ??????????? <taglib-uri>/tags/struts-html</taglib-uri>

    ??????????? <taglib-location>/WEB-INF/struts-html.tld</taglib-location>

    ??????? </taglib>

    ??????? <!-- 配置logic標(biāo)簽 -->

    ??????? <taglib>

    ??????????? <taglib-uri>/tags/struts-logic</taglib-uri>

    ??????????? <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>

    ??????? </taglib>

    ??? </jsp-config>

    </web-app>

    posted @ 2009-07-19 10:22 jadmin 閱讀(70) | 評(píng)論 (0)編輯 收藏

    6.3.2 Spring事務(wù)策略的優(yōu)勢(shì)

    雖然在上面的配置片段中,僅僅配置了JDBC局部事務(wù)管理器、Hibernate局部事務(wù)管理器、JDBC全局事務(wù)管理器等。但Spring支持大部分持久化策略的事務(wù)管理器。

    不論采用何種持久化策略,Spring都提供了一致的事務(wù)抽象,因此,應(yīng)用開(kāi)發(fā)者能在任何環(huán)境下,使用一致的編程模型。無(wú)須更改代碼,應(yīng)用就可在不同的事務(wù)管理策略中切換。Spring同時(shí)支持聲明式事務(wù)管理和編程式事務(wù)管理。

    使用編程式事務(wù)管理,開(kāi)發(fā)者使用的是Spring事務(wù)抽象,而無(wú)須使用任何具體的底層事務(wù)API。Spring的事務(wù)管理將代碼從底層具體的事務(wù)API中抽象出來(lái),該抽象可以使用在任何底層事務(wù)基礎(chǔ)之上。

    使用聲明式策略,開(kāi)發(fā)者通常書(shū)寫(xiě)很少的事務(wù)管理代碼,因此,不依賴Spring或任何其他事務(wù)API。Spring的聲明式事務(wù)無(wú)須任何額外的容器支持,Spring容器本身管理聲明式事務(wù)。使用聲明事務(wù)策略,無(wú)須在業(yè)務(wù)代碼中書(shū)寫(xiě)任何事務(wù)代碼,可以讓開(kāi)發(fā)者更好地專注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。Spring管理的事務(wù)支持多個(gè)事務(wù)資源的跨越,但無(wú)法支持跨越遠(yuǎn)程調(diào)用的事務(wù)上下文傳播。

    6.3.3 使用TransactionProxyFactoryBean創(chuàng)建事務(wù)代理

    Spring同時(shí)支持編程式事務(wù)策略和聲明式事務(wù)策略,大部分時(shí)候,都推薦采用聲明式事務(wù)策略,使用聲明事務(wù)策略的優(yōu)勢(shì)十分明顯:

    ?? ● 聲明式事務(wù)能大大降低開(kāi)發(fā)者的代碼書(shū)寫(xiě)量。而且聲明式事務(wù)幾乎不需要影響應(yīng)用的代碼。因此,無(wú)論底層事務(wù)策略如何變化,應(yīng)用程序無(wú)須任何改變。

    ?? ● 應(yīng)用程序代碼無(wú)須任何事務(wù)處理代碼,可以更專注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。

    ?? ● Spring則可對(duì)任何POJO的方法提供事務(wù)管理,而且Spring的聲明式事務(wù)管理無(wú)須容器的支持,可在任何環(huán)境下使用。

    ?? ● EJB的CMT無(wú)法提供聲明式回滾規(guī)則。而通過(guò)配置文件,Spring可指定事務(wù)在遇到特定異常時(shí)自動(dòng)回滾。Spring不僅可在代碼中使用setRollbackOnly回滾事務(wù),也可在配置文件中配置回滾規(guī)則。

    ?? ● 由于Spring采用AOP的方式管理事務(wù),因此,可以在事務(wù)回滾動(dòng)作中插入用戶自己的動(dòng)作,而不僅僅是執(zhí)行系統(tǒng)默認(rèn)的回滾。

    提示:本節(jié)不打算全面介紹Spring的各種事務(wù)策略,因此本節(jié)不會(huì)介紹編程式事務(wù)。如果讀者需要更全面了解Spring事務(wù)的相關(guān)方面,請(qǐng)參閱筆者所著的《Spring2.0寶典》???? 一書(shū)。

    對(duì)于采用聲明式事務(wù)策略,可以使用TransactionProxyFactoryBean來(lái)配置事務(wù)代理Bean。正如它的類名所暗示的,它是一個(gè)工廠Bean,工廠Bean用于生成一系列的Bean實(shí)例,這一系列的Bean實(shí)例都是Proxy。

    可能讀者已經(jīng)想到了,既然TransactionProxyFactoryBean產(chǎn)生的是代理Bean,可見(jiàn)這種事務(wù)代理正是基于Spring AOP組件的。配置TransactionProxyFactoryBean時(shí),一樣需要指定目標(biāo)Bean。

    每個(gè)TransactionProxyFactoryBean為一個(gè)目標(biāo)Bean生成事務(wù)代理,事務(wù)代理的方法改寫(xiě)了目標(biāo)Bean的方法,就是在目標(biāo)Bean的方法執(zhí)行之前加入開(kāi)始事務(wù),在目標(biāo)Bean的方法正常結(jié)束之前提交事務(wù),如果遇到特定異常則回滾事務(wù)。

    TransactionProxyFactoryBean創(chuàng)建事務(wù)代理時(shí),需要了解當(dāng)前事務(wù)所處的環(huán)境,該環(huán)境屬性通過(guò)PlatformTransactionManager實(shí)例傳入,而相關(guān)事務(wù)傳入規(guī)則在TransactionProxy- FactoryBean的定義中給出。

    下面給出聲明式事務(wù)配置文件的完整代碼:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->

    ??? <bean id="dataSource" class="com.mchange.v2.c3p0.
    ??? ComboPooledDataSource" destroy-method="close">

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->

    ??? ??? <property name="driverClass" value="com.mysql.jdbc.Driver"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->

    ??? ??? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶名 -->

    ??? ??? <property name="user" value="root"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->

    ??? ??? <property name="password" value="32147"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最大連接數(shù) -->

    ??? ??? <property name="maxPoolSize" value="40"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最小連接數(shù) -->

    ??? ??? <property name="minPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的初始化連接數(shù) -->

    ??? ??? <property name="initialPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的連接最大空閑時(shí)間 -->

    ??? ??? <property name="maxIdleTime" value="20"/>

    ??? </bean>

    ??? <!-- 定義Hibernate的SessionFactory -->

    ??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    ??? LocalSessionFactoryBean">

    ??????? <!-- 依賴注入SessionFactory所需的數(shù)據(jù)源,正是上文定義的dataSource -->

    ??????? <property name="dataSource" <ref="dataSource"/>

    ??????? <!-- mappingResources屬性用來(lái)列出全部映射文件 -->

    ??????? <property name="mappingResources">

    ??????????? <list>

    ??????????????? <!-- 以下用來(lái)列出所有的PO映射文件 -->

    ??????????????? <value>lee/Person.hbm.xml</value>

    ??????????? </list>

    ??????? </property>

    ????????? <!-- 定義Hibernate的SessionFactory屬性 -->

    ??????? <property name="hibernateProperties">

    ??????? ???? <props>

    ??????????????? <!-- 指定Hibernate的連接方言 -->

    ??????????? ??? <prop key="hibernate.dialect">org.hibernate.dialect.
    ??????????????? MySQLDialect</prop>

    ??????????????? <!-- 是否根據(jù)Hiberante映射創(chuàng)建數(shù)據(jù)表時(shí),選擇create、update、
    ??????????????? create-drop -->

    ??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>

    ??????? ???? </props>

    ??????? </property>

    ??? </bean>

    ??? <!-- 配置DAO Bean,該Bean將作為目標(biāo)Bean使用 -->

    ??? <bean id="personDAOTarget" class="lee.PersonDaoImpl">

    ??????? <!-- 采用依賴注入來(lái)傳入SessionFactory的引用 -->

    ??????? <property name="sessionFactory" ref="sessionFactory"/>

    ??? </bean>

    ??? <!-- 配置Hibernate的事務(wù)管理器 -->

    ??? <!-- 使用HibernateTransactionManager類,該類實(shí)現(xiàn)PlatformTransactionManager
    ??? 接口,針對(duì)采用Hibernate持久化連接的特定實(shí)現(xiàn) -->

    ??? <bean id="transactionManager"

    ??????? class="org.springframework.orm.hibernate3.
    ??????? HibernateTransactionManager">

    ??????? <!-- HibernateTransactionManager Bean,它需要依賴注入一個(gè)SessionFactory
    ??????? Bean的引用 -->

    ??? ??? <property name="sessionFactory" ref="sessionFactory"/>

    ??? </bean>

    ??? <!-- 配置personDAOTarget Bean的事務(wù)代理 -->

    ??? <bean id="personDAO"

    ??????? class="org.springframework.transaction.interceptor.
    ??????? TransactionProxyFactoryBean">

    ??????? <!-- 依賴注入PlatformTransactionManager的bean引用,此處使用
    ??????? Hibernate的bean -->

    ??????? <!-- 局部事務(wù)器,因此transactionManager 傳入Hibernate事務(wù)管理器的
    ??????? 引用 -->

    ??? ??? ?? <property name="transactionManager" ref="transactionManager"/>

    ??????? <!-- 需要生成代理的目標(biāo)bean -->

    ??? ??? ?? <property name="target" ref="personDAOTarget"/>

    ??????? <!-- 指定事務(wù)屬性 -->

    ??? ??? ?? <property name="transactionAttributes">

    ??????? ??? ?? <props>

    ??????????????? <!-- 以下部分為定義事務(wù)回滾規(guī)則 -->

    ??????????? ??? ?? <prop key="insert*">PROPAGATION_REQUIRED,
    ??????????????? -MyCheckedException</prop>

    ??????????? ??? ?? <prop key="update*">PROPAGATION_REQUIRED</prop>

    ??????????? ??? ?? <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>

    ??????? ??? </props>

    ??? ??? </property>

    ??? </bean>

    </beans>

    在上面的定義文件中,沒(méi)有對(duì)DAO對(duì)象采用Service層包裝。通常情況下,DAO層上應(yīng)有一層Service層。事務(wù)代理則以Service層Bean為目標(biāo)Bean。此處為了簡(jiǎn)化配置,TransactionProxyFactoryBean直接以DAO bean作為目標(biāo)bean,這一點(diǎn)不會(huì)影響事務(wù)代理的生成。

    事務(wù)回滾規(guī)則部分定義了三個(gè)回滾規(guī)則:

    第一個(gè)回滾規(guī)則表示所有以insert開(kāi)始的方法,都應(yīng)該滿足該事務(wù)規(guī)則。PROPAGATION_REQUIRED事務(wù)傳播規(guī)則指明,該方法必須處于事務(wù)環(huán)境中,如果當(dāng)前執(zhí)行線程已處于事務(wù)環(huán)境下,則直接執(zhí)行;否則,啟動(dòng)新的事務(wù)然后執(zhí)行該方法。該規(guī)則還指定,如果方法拋出MyCheckedException的實(shí)例及其子類的實(shí)例,則強(qiáng)制回滾。MyCheckedException前的“-”表示強(qiáng)制回滾;“+”則表示強(qiáng)制提交,即某些情況下,即使拋出異常也強(qiáng)制提交;

    第二個(gè)回滾規(guī)則表示所有以u(píng)pdate開(kāi)頭的方法,都遵守PROPAGATION_REQUIRED的事務(wù)傳播規(guī)則;

    第三個(gè)回滾規(guī)則表示除前面規(guī)定的方法外,其他所有方法都采用PROPAGATION_ REQUIRED事務(wù)傳播規(guī)則,而且只讀。

    常見(jiàn)的事務(wù)傳播規(guī)則有如下幾個(gè):

    ?? ● PROPAGATION_MANDATORY,要求調(diào)用該方法的線程必須處于事務(wù)環(huán)境中,否則拋出異常。

    ?? ● PROPAGATION_NESTED,如果執(zhí)行該方法的線程已處于事務(wù)環(huán)境下,依然啟動(dòng)新的事務(wù),方法在嵌套的事務(wù)里執(zhí)行。如果執(zhí)行該方法的線程序并未處于事務(wù)中,也啟動(dòng)新的事務(wù),然后執(zhí)行該方法,此時(shí)與PROPAGATION_REQUIRED相同。

    ?? ● PROPAGATION_NEVER,不允許調(diào)用該方法的線程處于事務(wù)環(huán)境下,如果調(diào)用該方法的線程處于事務(wù)環(huán)境下,則拋出異常。

    ?? ● PROPAGATION_NOT_SUPPORTED,如果調(diào)用該方法的線程處在事務(wù)中,則先暫停當(dāng)前事務(wù),然后執(zhí)行該方法。

    ?? ● PROPAGATION_REQUIRED,要求在事務(wù)環(huán)境中執(zhí)行該方法,如果當(dāng)前執(zhí)行線程已處于事務(wù)中,則直接調(diào)用;如果當(dāng)前執(zhí)行線程不處于事務(wù)中,則啟動(dòng)新的事務(wù)后執(zhí)行該方法。

    ?? ● PROPAGATION_REQUIRES_NEW,該方法要求有一個(gè)線程在新的事務(wù)環(huán)境中執(zhí)行,如果當(dāng)前執(zhí)行線程已處于事務(wù)中,先暫停當(dāng)前事務(wù),啟動(dòng)新的事務(wù)后執(zhí)行該方法;如果當(dāng)前調(diào)用線程不處于事務(wù)中,則啟動(dòng)新的事務(wù)后執(zhí)行該方法。

    ?? ● PROPAGATION_SUPPORTS,如果當(dāng)前執(zhí)行線程處于事務(wù)中,則使用當(dāng)前事務(wù),否則不使用事務(wù)。

    程序里原來(lái)使用personDAO的地方,無(wú)須變化。因?yàn)椋渲梦募飳ersonDAO目標(biāo)Bean的id改成personDAOTarget,為TransactionProxyFactoryBean工廠Bean所產(chǎn)生的代理Bean命名為personDAO。該代理Bean會(huì)包含原有personDAO的所有方法,而且為這些方法增加了不同的事務(wù)處理規(guī)則。

    程序面向PersonDaoImpl類所實(shí)現(xiàn)的接口編程,TransactionProxyFactoryBean生成的代理Bean也會(huì)實(shí)現(xiàn)TransactionProxyFactoryBean接口。因此,原有的程序中調(diào)用DAO組件的代碼無(wú)須任何改變。程序運(yùn)行時(shí),由事務(wù)代理完成原來(lái)目標(biāo)Bean完成的工作。

    事實(shí)上,Spring不僅支持對(duì)接口的代理,整合CGLIB后,Spring甚至可對(duì)具體類生成代理。只要設(shè)置proxyTargetClass屬性為true就可以。如果目標(biāo)Bean沒(méi)有實(shí)現(xiàn)任何接口,proxyTargetClass屬性默認(rèn)被設(shè)為true,此時(shí)Spring會(huì)對(duì)具體類生成代理。當(dāng)然,通常建議面向接口編程,而不要面向具體的實(shí)現(xiàn)類編程。

    6.3.4 使用繼承簡(jiǎn)化事務(wù)配置

    仔細(xì)觀察配置文件中兩個(gè)事務(wù)代理Bean的配置時(shí),發(fā)現(xiàn)兩個(gè)事務(wù)代理Bean的大部分配置完全相同,如果配置文件中包含大量這樣的事務(wù)代理Bean配置,配置文件將非常臃腫。考慮到大部分事務(wù)代理Bean的配置都大同小異,可以使用Bean繼承來(lái)簡(jiǎn)化事務(wù)代理的配置。

    正如前面部分介紹的,Bean繼承是將所有子Bean中相同的配置定義成一個(gè)模板,并將此模板Bean定義成一個(gè)抽象Bean。考慮所有事務(wù)代理Bean中,有如下部分是大致相?? 同的:

    ?? ● 事務(wù)代理Bean所使用的事務(wù)管理器。

    ?? ● 事務(wù)傳播規(guī)則。

    因此,現(xiàn)在配置文件中定義如下的事務(wù)代理模板Bean,其配置代碼如下:

    <!-- 定義所有事務(wù)代理Bean的模板 -->

    <bean id="txProxyTemplate" abstract="true"

    ??????? class="org.springframework.transaction.interceptor.
    ??????? TransactionProxyFactoryBean">

    ??? <!-- 為事務(wù)代理Bean注入生成代理所需的PlatformTransactionManager實(shí)例 -->

    ??? <property name="transactionManager" ref="transactionManager"/>

    ??????? <!-- 定義生成事務(wù)代理通用的事務(wù)屬性 -->

    ??????? <property name="transactionAttributes">

    ??????????? <props>

    ??????????????? <!-- 所有的方法都應(yīng)用PROPAGATION_REQUIRED的事務(wù)傳播規(guī)則 -->

    ??????????????? <prop key="*">PROPAGATION_REQUIRED</prop>

    ??????????? </props>

    ??? </property>

    </bean>

    而真正的事務(wù)代理Bean,則改為繼承上面的事務(wù)模板Bean。考慮到將目標(biāo)Bean定義在Spring容器中可能增加未知的風(fēng)險(xiǎn),因此將目標(biāo)Bean定義成嵌套Bean。

    <!-- 讓事務(wù)代理Bean繼承模板Bean -->

    <bean id="personDAO" parent="txProxyTemplate">

    ??? <!-- 這里采用嵌套Bean的方式來(lái)定義目標(biāo)Bean,當(dāng)然也可以引用已存在的Bean -->

    ??? <property name="target">

    ??????? <bean class="lee.personDAO"/>

    ??? </property>

    </bean>

    此時(shí)的personDAO Bean無(wú)須具體地定義事務(wù)屬性,它將在其父Bean txProxyTemplate中獲取事務(wù)定義屬性。此處采用嵌套Bean來(lái)定義目標(biāo)Bean,因此,并未將目標(biāo)Bean直接暴露在Spring的上下文中讓其他模塊調(diào)用。當(dāng)然,也可采用一個(gè)已經(jīng)存在的Bean作為目標(biāo)Bean;子Bean的事務(wù)屬性定義,完全可覆蓋事務(wù)代理模板里的事務(wù)屬性定義。如下例所示:

    <!-- 讓事務(wù)代理bean繼承模板Bean -->

    <bean id="personDAO" parent="txProxyTemplate">

    ??? <!-- 這里,采用引用已存在的bean的方式來(lái)定義目標(biāo)Bean -->

    ??? <property name="target" ref ="personDAOTarget"/>

    ??? <!-- 覆蓋事務(wù)代理模板bean中的事務(wù)屬性定義 -->

    ??? <property name="transactionAttributes">

    ??????? <props>

    ??????????? <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>

    ??????????? <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

    ??????????? <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>

    ??? ???? </props>

    </property>

    </bean>

    可見(jiàn),采用Bean繼承方式定義事務(wù)代理的方式,可以很好地簡(jiǎn)化事務(wù)代理的配置,可以避免配置事務(wù)代理Bean時(shí)的冗余配置。

    提示:使用Bean繼承可以很好地簡(jiǎn)化事務(wù)代理Bean的配置,通過(guò)將各事務(wù)代理Bean共同的配置信息提取成事務(wù)模板Bean,可以讓實(shí)際的事務(wù)代理Bean的配置更加簡(jiǎn)潔;而且,配置方式相當(dāng)直觀。盡量將目標(biāo)Bean配置成嵌套Bean,這樣的方式可以保證更好的內(nèi)聚性。

    如果讀者還記得前面介紹的AOP知識(shí),應(yīng)該知道還有一種更加簡(jiǎn)潔的配置,就是利用Bean后處理器,讓Bean后處理器為容器中其他Bean自動(dòng)創(chuàng)建事務(wù)代理。

    6.3.5 使用自動(dòng)創(chuàng)建代理簡(jiǎn)化事務(wù)配置

    回顧6.2.6節(jié)和6.2.7節(jié),讀者可能已經(jīng)想到如何自動(dòng)創(chuàng)建代理。是的,正是通過(guò)6.2.6節(jié)和6.2.7節(jié)所給出的兩個(gè)自動(dòng)代理創(chuàng)建類來(lái)生成事務(wù)代理。

    正如前文已經(jīng)提到的,使用BeanNameAutoProxyCreator和DefaultAdvisorAutoProxy- Creator來(lái)創(chuàng)建代理時(shí),并不一定是創(chuàng)建事務(wù)代理,關(guān)鍵在于傳入的攔截器,如果傳入事務(wù)攔截器,將可自動(dòng)生成事務(wù)代理。

    下面是使用BeanNameAutoProxyCreator自動(dòng)生成事務(wù)代理的配置文件:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及相應(yīng)的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->

    ??? <bean id="dataSource" class="com.mchange.v2.c3p0.
    ??? ComboPooledDataSource" destroy-method="close">

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->

    ??????? <property name="driverClass" value="com.mysql.jdbc.Driver"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->

    ??????? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶名 -->

    ??????? <property name="user" value="root"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->

    ??????? <property name="password" value="32147"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最大連接數(shù) -->

    ??????? <property name="maxPoolSize" value="40"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最小連接數(shù) -->

    ??????? <property name="minPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的初始化連接數(shù) -->

    ??????? <property name="initialPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的連接最大空閑時(shí)間 -->

    ??????? <property name="maxIdleTime" value="20"/>

    ??? </bean>

    ??? <!-- 使用JDBC的局部事務(wù)策略 -->

    ??? <bean id="transactionManager"

    ??????? class="org.springframework.jdbc.datasource.DataSource-
    ??????? TransactionManager">

    ??????? <!-- 為事務(wù)管理器注入所需的數(shù)據(jù)源Bean -->

    ??????? <property name="dataSource" ref="dataSource"/>

    ??? </bean>

    ??? <!-- 配置目標(biāo)Bean,該目標(biāo)Bean將由Bean后處理器自動(dòng)生成代理 -->

    ??? <bean id="test1" class="lee.TransactionTestImpl">

    ??????? <!-- 依賴注入目標(biāo)Bean所必需的數(shù)據(jù)源Bean -->

    ??? ??? <property name="ds" ref="dataSource"/>

    ??? </bean>

    ??? <!-- 配置目標(biāo)Bean,該目標(biāo)Bean將由Bean后處理器自動(dòng)生成代理 -->

    ??? <bean id="test2" class="lee.TestImpl">

    ??????? <!-- 依賴注入目標(biāo)Bean所必需的數(shù)據(jù)源Bean -->

    ??? ??? <property name="ds" ref="dataSource"/>

    ??? </bean>

    ??? <!-- 配置事務(wù)攔截器Bean -->

    ??? <bean id="transactionInterceptor"

    ??????? class="org.springframework.transaction.interceptor.
    ??????? TransactionInterceptor">

    ??????? <!-- 事務(wù)攔截器bean需要依賴注入一個(gè)事務(wù)管理器 -->

    ??????? <property name="transactionManager" ref="transactionManager"/>

    ??????? <property name="transactionAttributes">

    ??????????? <!-- 下面定義事務(wù)傳播屬性 -->

    ??????????? <props>

    ??????????????? <prop key="insert*">PROPAGATION_REQUIRED</prop>

    ??????????????? <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

    ??????????????? <prop key="*">PROPAGATION_REQUIRED</prop>

    ??????????? </props>

    ??????? </property>

    ??? </bean>

    ??? <!-- 定義BeanNameAutoProxyCreator的Bean后處理器 -->

    ??? <bean class="org.springframework.aop.framework.autoproxy.
    ??? BeanNameAutoProxyCreator">

    ??? <!-- 指定對(duì)滿足哪些bean name的bean自動(dòng)生成業(yè)務(wù)代理 -->

    ??? ??? <property name="beanNames">

    ??????????? <!-- 下面是所有需要自動(dòng)創(chuàng)建事務(wù)代理的Bean -->

    ??????????? <list>

    ??????????????? <value>test1</value>

    ??????????????? <value>test2</value>

    ??????????? </list>

    ??????????? <!-- 此處可增加其他需要自動(dòng)創(chuàng)建事務(wù)代理的Bean -->

    ??? ??? </property>

    ??????? <!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->

    ??????? <property name="interceptorNames">

    ??????????? <list>

    ??????????????? <value>transactionInterceptor</value>

    ??????????????? <!-- 此處可增加其他新的Interceptor -->

    ??????????? </list>

    ??????? </property>

    ??? </bean>

    </beans>

    如果配置文件中僅有兩個(gè)目標(biāo)Bean,可能不能很清楚地看出這種自動(dòng)創(chuàng)建代理配置方式的優(yōu)勢(shì),但如果有更多目標(biāo)Bean需要自動(dòng)創(chuàng)建事務(wù)代理,則可以很好地體會(huì)到這種配置方式的優(yōu)勢(shì):配置文件只需要簡(jiǎn)單地配置目標(biāo)Bean,然后在BeanNameAutoProxyCreator配置中增加一行即可。

    提示:使用BeanNameAutoProxyCreator可以自動(dòng)創(chuàng)建事務(wù)代理,使用DefaultAdvisor- AutoProxyCreator也可自動(dòng)創(chuàng)建事務(wù)代理。關(guān)于后一個(gè)Bean后處理器的配置方式,請(qǐng)參看前面6.2.7節(jié)的內(nèi)容。

    posted @ 2009-07-19 10:18 jadmin 閱讀(82) | 評(píng)論 (0)編輯 收藏

    6.3 Spring的事務(wù)

    Spring的事務(wù)管理不需與任何特定的事務(wù)API耦合。對(duì)不同的持久層訪問(wèn)技術(shù),編程式事務(wù)提供一致的事務(wù)編程風(fēng)格,通過(guò)模板化的操作一致性地管理事務(wù)。聲明式事務(wù)基于Spring AOP實(shí)現(xiàn),卻并不需要程序開(kāi)發(fā)者成為AOP專家,亦可輕易使用Spring的聲明式事務(wù)管理。

    6.3.1 Spring支持的事務(wù)策略

    Spring事務(wù)策略是通過(guò)PlatformTransactionManager接口體現(xiàn)的,該接口是Spring事務(wù)策略的核心。該接口的源代碼如下:

    public interface PlatformTransactionManager

    {

    ??? //平臺(tái)無(wú)關(guān)的獲得事務(wù)的方法

    ??? TransactionStatus getTransaction(TransactionDefinition definition)

    ??????? throws TransactionException;

    ??? //平臺(tái)無(wú)關(guān)的事務(wù)提交方法

    ??? void commit(TransactionStatus status) throws TransactionException;

    ??? //平臺(tái)無(wú)關(guān)的事務(wù)回滾方法

    ??? void rollback(TransactionStatus status) throws TransactionException;

    }

    PlatformTransactionManager是一個(gè)與任何事務(wù)策略分離的接口,隨著底層不同事務(wù)策略切換,應(yīng)用必須采用不同的實(shí)現(xiàn)類。PlatformTransactionManager接口沒(méi)有與任何事務(wù)資源捆綁在一起,它可以適應(yīng)于任何的事務(wù)策略,結(jié)合Spring的IoC容器,可以向PlatformTransactionManager注入相關(guān)的平臺(tái)特性。

    PlatformTransactionManager接口有許多不同的實(shí)現(xiàn)類,應(yīng)用程序面向與平臺(tái)無(wú)關(guān)的接口編程,對(duì)不同平臺(tái)的底層支持,由PlatformTransactionManager接口的實(shí)現(xiàn)類完成。從而,應(yīng)用程序無(wú)須與具體的事務(wù)API耦合。因此,使用PlatformTransactionManager接口,可將代碼從具體的事務(wù)API中解耦出來(lái)。

    即使使用特定容器管理的JTA,代碼依然無(wú)須執(zhí)行JNDI查找,無(wú)須與特定的JTA資源耦合在一起。通過(guò)配置文件,JTA資源傳給PlatformTransactionManager的實(shí)現(xiàn)類。因此,程序的代碼可在JTA事務(wù)管理和非JTA事務(wù)管理之間輕松切換。

    在PlatformTransactionManager接口內(nèi),包含一個(gè)getTransaction(TransactionDefinition definition)方法,該方法根據(jù)一個(gè)TransactionDefinition參數(shù),返回一個(gè)TransactionStatus對(duì)象。TransactionStatus對(duì)象表示一個(gè)事務(wù)。TransactionStatus被關(guān)聯(lián)在當(dāng)前執(zhí)行的線程。

    getTransaction(TransactionDefinition definition)返回的TransactionStatus對(duì)象,可能是一個(gè)新的事務(wù),也可能是一個(gè)已經(jīng)存在的事務(wù)對(duì)象。如果當(dāng)前執(zhí)行的線程已經(jīng)處于事務(wù)管理下,返回當(dāng)前線程的事務(wù)對(duì)象,否則,返回當(dāng)前線程的調(diào)用堆棧已有的事務(wù)對(duì)象。

    TransactionDefinition接口定義了一個(gè)事務(wù)規(guī)則,該接口必須指定如下幾個(gè)屬性值:

    ?? ● 事務(wù)隔離,當(dāng)前事務(wù)和其他事務(wù)的隔離程度。例如,這個(gè)事務(wù)能否看到其他事務(wù)未提交的數(shù)據(jù)等。

    ?? ● 事務(wù)傳播,通常,在事務(wù)中執(zhí)行的代碼都會(huì)在當(dāng)前事務(wù)中運(yùn)行。但是,如果一個(gè)事務(wù)上下文已經(jīng)存在,有幾個(gè)選項(xiàng)可指定該事務(wù)性方法的執(zhí)行行為。例如,大多數(shù)情況下,簡(jiǎn)單地在現(xiàn)有的事務(wù)上下文中運(yùn)行;或者掛起現(xiàn)有事務(wù),創(chuàng)建一個(gè)新的事務(wù)。Spring提供EJB CMT(Contain Manager Transaction,容器管理事務(wù))中所有的事務(wù)傳播選項(xiàng)。

    ?? ● 事務(wù)超時(shí),事務(wù)在超時(shí)前能運(yùn)行多久。事務(wù)的最長(zhǎng)持續(xù)時(shí)間。如果事務(wù)一直沒(méi)有被提交或回滾,將在超出該時(shí)間后,系統(tǒng)自動(dòng)回滾事務(wù)。

    ?? ● 只讀狀態(tài),只讀事務(wù)不修改任何數(shù)據(jù)。在某些情況下(例如使用Hibernate時(shí)),只讀事務(wù)是非常有用的優(yōu)化。

    TransactionStatus代表事務(wù)本身,它提供了簡(jiǎn)單的控制事務(wù)執(zhí)行和查詢事務(wù)狀態(tài)的方法。這些方法在所有的事務(wù)API中都是相同的。TransactionStatus接口的源代碼如下:

    public interface TransactionStatus

    {

    ??? //判斷事務(wù)是否是新建的事務(wù)

    ??? boolean isNewTransaction();

    ??? //設(shè)置事務(wù)回滾

    ??? void setRollbackOnly();

    ??? //查詢事務(wù)是否已有回滾標(biāo)志

    ??? boolean isRollbackOnly();

    }

    Spring的事務(wù)管理由PlatformTransactionManager的不同實(shí)現(xiàn)類完成。在Spring上下文中配置PlatformTransactionManager Bean時(shí),必須針對(duì)不同環(huán)境提供不同的實(shí)現(xiàn)類。

    下面提供不同的持久層訪問(wèn)環(huán)境,及其對(duì)應(yīng)的PlatformTransactionManager實(shí)現(xiàn)類的 配置。

    JDBC數(shù)據(jù)源的局部事務(wù)策略:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans

    ?????? http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

    ??? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->

    ??? <property name="driverClass" value="com.mysql.jdbc.Driver"/>

    ??? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->

    ??? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

    ??? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶名 -->

    ??? <property name="user" value="root"/>

    ??? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->

    ??? <property name="password" value="32147"/>

    ??? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最大連接數(shù) -->

    ??? <property name="maxPoolSize" value="40"/>

    ??? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最小連接數(shù) -->

    ??? <property name="minPoolSize" value="1"/>

    ??? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的初始化連接數(shù) -->

    ??? <property name="initialPoolSize" value="1"/>

    ??? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的連接最大空閑時(shí)間 -->

    ??? <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 配置JDBC數(shù)據(jù)源的局部事務(wù)管理器 -->

    <!-- 使用DataSourceTransactionManager 類,該類實(shí)現(xiàn)PlatformTransactionManager接口 -->

    <!-- 針對(duì)采用數(shù)據(jù)源連接的特定實(shí)現(xiàn) -->

    <bean id="transactionManager"

    ??????? class="org.springframework.jdbc.datasource.
    ??????? DataSourceTransactionManager">

    ??????? <!-- DataSourceTransactionManager bean需要依賴注入一個(gè)DataSource
    ??????? bean的引用 -->

    ??? ???? <property name="dataSource" ref="dataSource"/>

    ??? </bean>

    </beans>

    對(duì)于容器管理JTA數(shù)據(jù)源,全局事務(wù)策略的配置文件如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 配置JNDI數(shù)據(jù)源Bean -->

    ??? <bean id="dataSource" class="org.springframework.jndi.
    ??? JndiObjectFactoryBean">

    ??? <!-- 容器管理數(shù)據(jù)源的JNDI -->

    ??? ???? <property name="jndiName" value="jdbc/jpetstore"/>

    ??? </bean>

    ??? <!-- 使用JtaTransactionManager類,該類實(shí)現(xiàn)PlatformTransactionManager接
    ??? 口 -->

    ??? <!-- 針對(duì)采用全局事務(wù)管理的特定實(shí)現(xiàn) -->

    ??? <!-- JtaTransactionManager不需要知道數(shù)據(jù)源,或任何其他特定資源 -->

    ??? <!-- 因?yàn)樗褂萌萜鞯娜质聞?wù)管理 -->

    ??? <bean id="transactionManager"

    ??????? class="org.springframework.transaction.jta.
    ??????? JtaTransactionManager" />

    </beans>

    對(duì)于采用Hibernate持久層訪問(wèn)策略時(shí),局部事務(wù)策略的配置文件如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->

    ??? <bean id="dataSource" class="com.mchange.v2.c3p0.
    ??? ComboPooledDataSource" destroy-method="close">

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->

    ??? ??? <property name="driverClass" value="com.mysql.jdbc.Driver"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->

    ??? ??? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶名 -->

    ??? ??? <property name="user" value="root"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->

    ??? ??? <property name="password" value="32147"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最大連接數(shù) -->

    ??? ??? <property name="maxPoolSize" value="40"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的最小連接數(shù) -->

    ??? ??? <property name="minPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的初始化連接數(shù) -->

    ??? ??? <property name="initialPoolSize" value="1"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)連接池的連接最大空閑時(shí)間 -->

    ??? ??? <property name="maxIdleTime" value="20"/>

    ??? </bean>

    ??? <!-- 定義Hibernate的SessionFactory -->

    ??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    ??? LocalSessionFactoryBean">

    ??????? <!-- 依賴注入SessionFactory所需的數(shù)據(jù)源,正是上文定義的dataSource -->

    ??????? <property name="dataSource" ref="dataSource"/>

    ??????? <!-- mappingResources屬性用來(lái)列出全部映射文件 -->

    ??????? <property name="mappingResources">

    ??????????? <list>

    ??????????????? <!-- 以下用來(lái)列出所有的PO映射文件 -->

    ??????????????? <value>lee/MyTest.hbm.xml</value>

    ??????????? </list>

    ??????? </property>

    ????????? <!-- 定義Hibernate的SessionFactory的屬性 -->

    ??????? <property name="hibernateProperties">

    ??????? ???? <props>

    ??????????????? <!-- 指定Hibernate的連接方言 -->

    ??????????? ??? <prop key="hibernate.dialect">org.hibernate.dialect.
    ??????????? ??? MySQLDialect</prop>

    ??????????????? <!-- 是否根據(jù)Hibernate映射創(chuàng)建數(shù)據(jù)表時(shí),選擇create、update、
    ??????????????? create-drop -->

    ??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>

    ??????? ???? </props>

    ??????? </property>

    ??? </bean>

    ??? <!-- 配置Hibernate的局部事務(wù)管理器 -->

    ??? <!-- 使用HibernateTransactionManager類,該類是PlatformTransactionManager
    ??? 接口,針對(duì)采用Hibernate持久化連接的特定實(shí)現(xiàn) -->

    ??? <bean id="transactionManager"

    ??? class="org.springframework.orm.hibernate3.
    ??? HibernateTransactionManager">

    ??????????? <!-- HibernateTransactionManager Bean需要依賴注入一個(gè)
    ??????????? SessionFactorybean的引用 -->

    ??? ???? <property name="sessionFactory" ref="sessionFactory"/>

    ???? </bean>

    </beans>

    對(duì)于采用Hibernate持久層訪問(wèn)策略時(shí),全局事務(wù)策略的配置文件如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 配置JNDI數(shù)據(jù)源Bean -->

    ??? <bean id="dataSource" class="org.springframework.jndi.
    ??? JndiObjectFactoryBean">

    ??????? <!-- 容器管理數(shù)據(jù)源的JNDI -->

    ??? ???? <property name="jndiName" value="jdbc/jpetstore"/>

    ??? </bean>

    ??? <!--定義Hibernate的SessionFactory -->

    ??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    ??? LocalSessionFactoryBean">

    ??????? <!-- 依賴注入SessionFactory所需的數(shù)據(jù)源,正是上文定義的dataSource Bean -->

    ??????? <property name="dataSource" ref="dataSource"/>

    ??????? <!-- mappingResources屬性用來(lái)列出全部映射文件 -->

    ??????? <property name="mappingResources">

    ??????????? <list>

    ??? ????????????? <!-- 以下用來(lái)列出所有的PO映射文件 -->

    ??????????????? <value>lee/MyTest.hbm.xml</value>

    ??????????? </list>

    ??????? </property>

    ????????? <!-- 定義Hibernate的SessionFactory的屬性 -->

    ??????? <property name="hibernateProperties">

    ??????? ???? <props>

    ??????????????? <!-- 指定Hibernate的連接方言 -->

    ??????????? ??? <prop key="hibernate.dialect">org.hibernate.dialect.
    ??????????????? MySQLDialect</prop>

    ??????????????? <!-- 是否根據(jù)Hiberante映射創(chuàng)建數(shù)據(jù)表時(shí),選擇create、update、
    ??????????????? create-drop -->

    ??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>

    ??????? ???? </props>

    ???? ???? </property>

    ??? </bean>

    ??? <!-- 使用JtaTransactionManager類,該類是PlatformTransactionManager接口,
    ??????????? 針對(duì)采用數(shù)據(jù)源連接的特定實(shí)現(xiàn) -->

    ??? <!-- JtaTransactionManager不需要知道數(shù)據(jù)源,或任何其他特定資源,

    ??????????? 因?yàn)槭褂萌萜鞯娜质聞?wù)管理 -->

    ??? <bean id="transactionManager"

    ??????? ?? class="org.springframework.transaction.jta.
    ??????? ?? JtaTransactionManager" />

    </beans>

    不論采用哪種持久層訪問(wèn)技術(shù),只要使用JTA數(shù)據(jù)源,Spring事務(wù)管理器的配置都是一樣的,因?yàn)樗鼈兌疾捎玫氖侨质聞?wù)管理。

    可以看到,僅僅通過(guò)配置文件的修改,就可以在不同的事務(wù)管理策略間切換,即使從局部事務(wù)到全局事務(wù)的切換。

    提示:Spring所支持的事務(wù)策略非常靈活,Spring的事務(wù)策略允許應(yīng)用程序在不同事務(wù)策略之間自由切換,即使需要在局部事務(wù)策略和全局事務(wù)策略之間切換,只需要修改配置文件,而應(yīng)用程序的代碼無(wú)須任何改變。這種靈活的設(shè)計(jì),又何嘗不是因?yàn)槊嫦蚪涌诰幊處?lái)的優(yōu)勢(shì),可見(jiàn)面向接口編程給應(yīng)用程序更好的適應(yīng)性。

    posted @ 2009-07-19 10:18 jadmin 閱讀(78) | 評(píng)論 (0)編輯 收藏

    6.2.4 代理接口

    當(dāng)目標(biāo)Bean的實(shí)現(xiàn)類實(shí)現(xiàn)了接口后,Spring AOP可以為其創(chuàng)建JDK動(dòng)態(tài)代理,而無(wú)須使用CGLIB創(chuàng)建的代理,這種代理稱為代理接口。

    創(chuàng)建AOP代理必須指定兩個(gè)屬性:目標(biāo)Bean和處理。實(shí)際上,很多AOP框架都以攔截器作為處理。因?yàn)镾pring AOP與IoC容器的良好整合,因此配置代理Bean時(shí),完全可以利用依賴注入來(lái)管理目標(biāo)Bean和攔截器Bean。

    下面的示例演示了基于AOP的權(quán)限認(rèn)證,它是簡(jiǎn)單的TestService接口,該接口模擬Service組件,該組件內(nèi)包含兩個(gè)方法:

    ?? ● 查看數(shù)據(jù)。

    ?? ● 修改數(shù)據(jù)。

    接口的源代碼如下:

    //Service組件接口

    public interface TestService

    {

    ??? //查看數(shù)據(jù)

    ??? void view();

    ??? //修改數(shù)據(jù)

    ??? void modify();

    }

    該接口的實(shí)現(xiàn)類實(shí)現(xiàn)兩個(gè)方法。因?yàn)槠拗疲臼纠⑽达@示出完整的查看數(shù)據(jù)和修改數(shù)據(jù)的持久層操作,僅僅在控制臺(tái)打印兩行信息。實(shí)際的項(xiàng)目實(shí)現(xiàn)中,兩個(gè)方法的實(shí)現(xiàn)則改成對(duì)持久層組件的調(diào)用,這不會(huì)影響示例程序的效果。實(shí)現(xiàn)類的源代碼如下:

    TestService接口的實(shí)現(xiàn)類

    public class TestServiceImpl implements TestService

    {

    ??? //實(shí)現(xiàn)接口必須實(shí)現(xiàn)的方法

    ??? public void view()

    ??? {

    ??????? System.out.println("用戶查看數(shù)據(jù)");

    ??? }

    ??? //實(shí)現(xiàn)接口必須實(shí)現(xiàn)的方法

    ??? public void modify()

    ??? {

    ??????? System.out.println("用戶修改數(shù)據(jù)");

    ??? }

    }

    示例程序采用Around 處理作為攔截器,攔截器中使用依賴注入獲得當(dāng)前用戶名。實(shí)際Web應(yīng)用中,用戶應(yīng)該從session中讀取。這不會(huì)影響示例代碼的效果。攔截器源代碼如下:

    public class AuthorityInterceptor implements MethodInterceptor

    {

    ??? //當(dāng)前用戶名

    ??? private String user;

    ??? //依賴注入所必需的setter方法

    ??? public void setUser(String user)

    ??? {

    ??????? this.user = user;

    ??? }

    ??? public Object invoke(MethodInvocation invocation) throws Throwable

    ??? {

    ??????? //獲取當(dāng)前攔截的方法名

    ??????? String methodName = invocation.getMethod().getName();

    ??????? //下面執(zhí)行權(quán)限檢查

    ??????? //對(duì)既不是管理員,也不是注冊(cè)用戶的情況

    ??????? if (!user.equals("admin") && !user.equals("registedUser"))

    ??????? {

    ??????????? System.out.println("您無(wú)權(quán)執(zhí)行該方法");

    ??????????? return null;

    ??????? }

    ??????? //對(duì)僅僅是注冊(cè)用戶,調(diào)用修改數(shù)據(jù)的情況

    ??????? else if (user.equals("registedUser") && methodName.equals
    ??????? ("modify"))

    ??????? {

    ??????????? System.out.println("您不是管理員,無(wú)法修改數(shù)據(jù)");

    ??????????? return null;

    ??????? }

    ??????? //對(duì)管理員或注冊(cè)用戶,查看數(shù)據(jù)的情況

    ??????? else

    ??????? {

    ??????????? return invocation.proceed();

    ??????? }

    ??? }

    }

    TestAction類依賴TestService。因篇幅關(guān)系,此處不給出TestAction的接口的源代碼,TestActionImpl的源代碼如下:

    public class TestActionImpl

    {

    ??? //將TestService作為成員變量,面向接口編程

    ??? private TestService ts;

    ??? //依賴注入的setter方法

    ??? public void setTs(TestService ts)

    ??? {

    ??????? this.ts = ts;

    ??? }

    ??? //修改數(shù)據(jù)

    ??? public void modify()

    ??? {

    ??????? ts.modify();

    ??? }

    ??? //查看數(shù)據(jù)

    ??? public void view()

    ??? {

    ??????? ts.view();

    ??? }

    }

    配置文件如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans

    ?????? http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 配置目標(biāo)Bean -->

    ??? <bean id="serviceTarget" class="lee.TestServiceImpl"/>

    ??? <!-- 配置攔截器,攔截器作為處理使用 -->

    ??? <bean id="authorityInterceptor" class="lee.AuthorityInterceptor">

    ??????? <property name="user" value="admin"/>

    ??? </bean>

    ??? <!-- 配置代理工廠Bean,負(fù)責(zé)生成AOP代理 -->

    ??? <bean id="service" class="org.springframework.aop.framework.
    ??? ProxyFactoryBean">

    ??????? <!-- 指定AOP代理所實(shí)現(xiàn)的接口 -->

    ??????? <property name="proxyInterfaces" value="lee.TestService"/>

    ??????? <!-- 指定AOP代理所代理的目標(biāo)Bean -->

    ??????? <property name="target" ref="serviceTarget"/>

    ??????? <!-- AOP代理所需要的攔截器列表 -->

    ??????? <property name="interceptorNames">

    ??????????? <list>

    ??????????????? <value>authorityInterceptor</value>

    ??????????? </list>

    ??????? </property>

    ??? </bean>

    ??? <!-- 配置Action Bean,該Action依賴TestService Bean -->

    ??? <bean id="testAction" class="lee.TestActionImpl">

    ??????? <!-- 此處注入的是依賴代理Bean -->

    ??????? <property name="ts" ref="service"/>

    ??? </bean>

    </beans>

    主程序請(qǐng)求testAction Bean,然后調(diào)用該Bean的兩個(gè)方法,主程序如下:

    public class BeanTest

    {

    ??? public static void main(String[] args)throws Exception

    ??? {

    ??????? //創(chuàng)建Spring容器實(shí)例

    ??????? ApplicationContext ctx = new FileSystemXmlApplicationContext
    ??????? ("bean.xml");

    ??????? //獲得TestAction bean

    ??????? TestAction ta = (TestAction)ctx.getBean("testAction");

    ??????? //調(diào)用bean的兩個(gè)測(cè)試方法

    ??????? ta.view();

    ??????? ta.modify();

    ??? }

    }

    程序執(zhí)行結(jié)果如下:

    [java] 用戶查看數(shù)據(jù)

    [java] 用戶修改數(shù)據(jù)

    代理似乎沒(méi)有發(fā)揮任何作用。因?yàn)榕渲梦募械漠?dāng)前用戶是admin,admin用戶具備訪問(wèn)和修改數(shù)據(jù)的權(quán)限,因此代理并未阻止訪問(wèn)。將配置文件中的admin修改成registed- User,再次執(zhí)行程序,得到如下結(jié)果:

    [java] 用戶查看數(shù)據(jù)

    [java] 您不是管理員,無(wú)法修改數(shù)據(jù)

    代理阻止了registedUser修改數(shù)據(jù),查看數(shù)據(jù)可以執(zhí)行。將registedUser修改成其他用戶,執(zhí)行程序,看到如下結(jié)果:

    [java] 您無(wú)權(quán)執(zhí)行該方法

    [java] 您無(wú)權(quán)執(zhí)行該方法

    代理阻止用戶對(duì)兩個(gè)方法的執(zhí)行。基于AOP的權(quán)限檢查,可以降低程序的代碼量,因?yàn)闊o(wú)須每次調(diào)用方法之前,手動(dòng)編寫(xiě)權(quán)限檢查代碼;同時(shí),權(quán)限檢查與業(yè)務(wù)邏輯分離,提高了程序的解耦。

    示例中的目標(biāo)Bean被暴露在容器中,可以被客戶端代碼直接訪問(wèn)。為了避免客戶端代碼直接訪問(wèn)目標(biāo)Bean,可以將目標(biāo)Bean定義成代理工廠的嵌套Bean,修改后的配置文件如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans

    ?????? http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 配置攔截器,攔截器作為處理使用 -->

    ??? <bean id="authorityInterceptor" class="lee.AuthorityInterceptor">

    ??????? <property name="user" value="admin"/>

    ??? </bean>

    ??? <!-- 配置代理工廠Bean,該工廠Bean將負(fù)責(zé)創(chuàng)建目標(biāo)Bean的代理 -->

    ??? <bean id="service" class="org.springframework.aop.framework.
    ??? ProxyFactoryBean">

    ??????? <!-- 指定AOP代理所實(shí)現(xiàn)的接口 -->

    ??????? <property name="proxyInterfaces" value="lee.TestService"/>

    ??????? <property name="target">

    ??????????? <!-- 以嵌套Bean的形式定義目標(biāo)Bean,避免客戶端直接訪問(wèn)目標(biāo)Bean -->

    ??? ??????? <bean class="lee.TestServiceImpl"/>

    ??????? </property>

    ??????? <!-- AOP代理所需要的攔截器列表 -->

    ??????? <property name="interceptorNames">

    ??????????? <list>

    ??????????????? <value>authorityInterceptor</value>

    ??????????? </list>

    ??????? </property>

    ??? </bean>

    ??? <!-- 配置Action Bean,該Action依賴TestService Bean -->

    ??? <bean id="testAction" class="lee.TestActionImpl">

    ??????? <!-- 此處注入的是依賴代理Bean -->

    ??????? <property name="ts" ref="service"/>

    ??? </bean>

    </beans>

    由上面介紹的內(nèi)容可見(jiàn),Spring的AOP是對(duì)JDK動(dòng)態(tài)代理模式的深化。通過(guò)Spring AOP組件,允許通過(guò)配置文件管理目標(biāo)Bean和AOP所需的處理。

    下面將繼續(xù)介紹如何為沒(méi)有實(shí)現(xiàn)接口的目標(biāo)Bean創(chuàng)建CGLIB代理。

    6.2.5 代理類

    如果目標(biāo)類沒(méi)有實(shí)現(xiàn)接口,則無(wú)法創(chuàng)建JDK動(dòng)態(tài)代理,只能創(chuàng)建CGLIB代理。如果需要沒(méi)有實(shí)現(xiàn)接口的Bean實(shí)例生成代理,配置文件中應(yīng)該修改如下兩項(xiàng):

    ?? ● 去掉<property name="proxyInterfaces"/>聲明。因?yàn)椴辉俅斫涌冢虼耍颂幍呐渲脹](méi)有意義。

    ?? ● 增加<property name="proxyTargetClass">子元素,并設(shè)其值為true,通過(guò)該元素強(qiáng)制使用CGLIB代理,而不是JDK動(dòng)態(tài)代理。

    注意:最好面向接口編程,不要面向類編程。同時(shí),即使在實(shí)現(xiàn)接口的情況下,也可強(qiáng)制使用CGLIB代理。

    CGLIB代理在運(yùn)行期間產(chǎn)生目標(biāo)對(duì)象的子類,該子類通過(guò)裝飾器設(shè)計(jì)模式加入到Advice中。因?yàn)镃GLIB代理是目標(biāo)對(duì)象的子類,則必須考慮保證如下兩點(diǎn):

    ?? ● 目標(biāo)類不能聲明成final,因?yàn)閒inal類不能被繼承,無(wú)法生成代理。

    ?? ● 目標(biāo)方法也不能聲明成final,final方法不能被重寫(xiě),無(wú)法得到處理。

    當(dāng)然,為了需要使用CGLIB代理,應(yīng)用中應(yīng)添加CGLIB二進(jìn)制Jar文件。

    6.2.6 使用BeanNameAutoProxyCreator自動(dòng)創(chuàng)建代理

    這是一種自動(dòng)創(chuàng)建事務(wù)代理的方式,一旦在容器中配置了BeanNameAutoProxyCreator實(shí)例,該實(shí)例將會(huì)對(duì)指定名字的Bean實(shí)例自動(dòng)創(chuàng)建代理。實(shí)際上,BeanNameAutoProxyCreator是一個(gè)Bean后處理器,理論上它會(huì)對(duì)容器中所有的Bean進(jìn)行處理,實(shí)際上它只對(duì)指定名字的Bean實(shí)例創(chuàng)建代理。

    BeanNameAutoProxyCreator根據(jù)名字自動(dòng)生成事務(wù)代理,名字匹配支持通配符。

    與ProxyFactoryBean一樣,BeanNameAutoProxyCreator需要一個(gè)interceptorNames屬性,該屬性名雖然是“攔截器”,但并不需要指定攔截器列表,它可以是Advisor或任何處理類型。

    下面是使用BeanNameAutoProxyCreator的配置片段:

    <!-- 定義事務(wù)攔截器bean -->

    <bean id="transactionInterceptor"

    ??? class="org.springframework.transaction.interceptor.
    ??? TransactionInterceptor">

    ??? <!-- 事務(wù)攔截器bean需要依賴注入一個(gè)事務(wù)管理器 -->

    ??? <property name="transactionManager" ref="transactionManager"/>

    ??? <property name="transactionAttributes">

    ??????? <!-- 下面定義事務(wù)傳播屬性 -->

    ??????? <props>

    ??????????? <prop key="insert*">PROPAGATION_REQUIRED </prop>

    ??????????? <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

    ??????????? <prop key="*">PROPAGATION_REQUIRED</prop>

    ??????? </props>

    ??? </property>

    ??? </bean>

    <!-- 定義BeanNameAutoProxyCreator Bean,它是一個(gè)Bean后處理器,

    ??????? 負(fù)責(zé)為容器中特定的Bean創(chuàng)建AOP代理 -->

    <bean class="org.springframework.aop.framework.autoproxy.
    ??? BeanNameAutoProxyCreator">

    ??? <!-- 指定對(duì)滿足哪些bean name的bean自動(dòng)生成業(yè)務(wù)代理 -->

    ??? <property name="beanNames">

    ??????? <list>

    ??????????? <!-- 下面是所有需要自動(dòng)創(chuàng)建事務(wù)代理的Bean -->

    ??????? ??? <value>core-services-applicationControllerSevice</value>

    ??????? ??? <value>core-services-deviceService</value>

    ??????? ??? <value>core-services-authenticationService</value>

    ??????? ??? <value>core-services-packagingMessageHandler</value>

    ??????? ??? <value>core-services-sendEmail</value>

    ??????? ??? <value>core-services-userService</value>

    ??????????? <!-- 此處可增加其他需要自動(dòng)創(chuàng)建事務(wù)代理的Bean -->

    ??????? </list>

    ??? </property>

    ??? <!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->

    ??? <property name="interceptorNames">

    ??????? <list>

    ??????????? <value>transactionInterceptor</value>

    ??????????? <!-- 此處可增加其他新的Interceptor -->

    ??????? </list>

    ??? </property>

    </bean>

    上面的片段是使用BeanNameAutoProxyCreator自動(dòng)創(chuàng)建事務(wù)代理的片段。Transaction- Interceptor用來(lái)定義事務(wù)攔截器,定義事務(wù)攔截器時(shí)傳入事務(wù)管理器Bean,也指定事務(wù)傳播屬性。

    通過(guò)BeanNameAutoProxyCreator定義Bean后處理器,定義該Bean后處理器時(shí),通過(guò)beanNames屬性指定有哪些目標(biāo)Bean生成事務(wù)代理;還需要指定“攔截器鏈”,該攔截器鏈可以由任何處理組成。

    定義目標(biāo)Bean還可使用通配符,使用通配符的配置片段如下所示:

    <!-- 定義BeanNameAutoProxyCreator Bean,它是一個(gè)Bean后處理器,

    ??????? 負(fù)責(zé)為容器中特定的Bean創(chuàng)建AOP代理 -->

    <bean class="org.springframework.aop.framework.autoproxy.
    BeanNameAutoProxyCreator">

    ??? <!-- 指定對(duì)滿足哪些bean name的bean自動(dòng)生成業(yè)務(wù)代理 -->

    ??? <property name="beanNames">

    ??????? <!-- 此處使用通配符確定目標(biāo)bean -->

    ??????? <value>*DAO,*Service,*Manager</value>

    ??? </property>

    ??? <!-- 下面定義BeanNameAutoProxyCreator所需的事務(wù)攔截器 -->

    ??? <property name="interceptorNames">

    ??????? <list>

    ??????????? <value>transactionInterceptor</value>

    ??????????? <!-- 此處可增加其他新的Interceptor -->

    ??????? </list>

    ??? </property>

    </bean>

    上面的配置片段中,所有名字以DAO、Service、Manager結(jié)尾的bean,將由該“bean后處理器”為其創(chuàng)建事務(wù)代理。目標(biāo)bean不再存在,取而代之的是目標(biāo)bean的事務(wù)代理。通過(guò)這種方式,不僅可以極大地降低配置文件的繁瑣,而且可以避免客戶端代碼直接調(diào)用目標(biāo)bean。

    注意:雖然上面的配置片段是為目標(biāo)對(duì)象自動(dòng)生成事務(wù)代理。但這不是唯一的,如果有需要,可以為目標(biāo)對(duì)象生成任何的代理。BeanNameAutoProxyCreator為目標(biāo)對(duì)象生成怎樣的代理,取決于傳入怎樣的處理Bean,如果傳入事務(wù)攔截器,則生成事務(wù)代理Bean;否則將生成其他代理,在后面的實(shí)例部分,讀者將可以看到大量使用BeanNameAutoProxy- Creator創(chuàng)建的權(quán)限檢查代理。

    6.2.7 使用DefaultAdvisorAutoProxyCreator自動(dòng)創(chuàng)建代理

    Spring還提供了另一個(gè)Bean后處理器,它也可為容器中的Bean自動(dòng)創(chuàng)建代理。相比之下,DefaultAdvisorAutoProxyCreator是更通用、更強(qiáng)大的自動(dòng)代理生成器。它將自動(dòng)應(yīng)用于當(dāng)前容器中的advisor,不需要在DefaultAdvisorAutoProxyCreator定義中指定目標(biāo)Bean的名字字符串。

    這種定義方式有助于配置的一致性,避免在自動(dòng)代理創(chuàng)建器中重復(fù)配置目標(biāo)Bean 名。

    使用該機(jī)制包括:

    ?? ● 配置DefaultAdvisorAutoProxyCreator bean定義。

    ?? ● 配置任何數(shù)目的Advisor,必須是Advisor,不僅僅是攔截器或其他處理。因?yàn)椋仨毷褂们腥朦c(diǎn)檢查處理是否符合候選Bean定義。

    DefaultAdvisorAutoProxyCreator計(jì)算Advisor包含的切入點(diǎn),檢查處理是否應(yīng)該被應(yīng)用到業(yè)務(wù)對(duì)象,這意味著任何數(shù)目的Advisor都可自動(dòng)應(yīng)用到業(yè)務(wù)對(duì)象。如果Advisor中沒(méi)有切入點(diǎn)符合業(yè)務(wù)對(duì)象的方法,這個(gè)對(duì)象就不會(huì)被代理。如果增加了新的業(yè)務(wù)對(duì)象,只要它們符合切入點(diǎn)定義,DefaultAdvisorAutoProxyCreator將自動(dòng)為其生成代理,無(wú)須額外?? 配置。

    當(dāng)有大量的業(yè)務(wù)對(duì)象需要采用相同處理,DefaultAdvisorAutoProxyCreator是非常有用的。一旦定義恰當(dāng),直接增加業(yè)務(wù)對(duì)象,而不需要額外的代理配置,系統(tǒng)自動(dòng)為其增加???? 代理。

    <beans>

    ??? <!-- 定義Hibernate局部事務(wù)管理器,可切換到JTA全局事務(wù)管理器 -->

    ??? <bean id="transactionManager"

    ???????? class="org.springframework.orm.hibernate3.
    ??????? HibernateTransactionManager">

    ??????? <!-- 定義事務(wù)管理器時(shí),依賴注入SessionFactory -->

    ???????? <property name="sessionFactory" ref bean="sessionFactory"/>

    ??? </bean>

    ??? <!-- 定義事務(wù)攔截器 -->

    ??? <bean id="transactionInterceptor"

    ??????? class="org.springframework.transaction.interceptor.
    ??????? TransactionInterceptor">

    ???????? <property name="transactionManager" ref="transactionManager"/>

    ???????? <property name="transactionAttributeSource">

    ??????????? <props>

    ??????????????? <prop key="*">PROPAGATION_REQUIRED</prop>

    ????? ??????????? <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

    ??????????? </props>

    ??????? </property>

    ??? </bean>

    ??? <!-- 定義DefaultAdvisorAutoProxyCreator Bean,這是一個(gè)Bean后處理器 -->

    ??? <bean class="org.springframework.aop.framework.autoproxy.
    ??? DefaultAdvisorAutoProxyCreator"/>

    ??? <!-- 定義事務(wù)Advisor -->

    ??? <bean class="org.springframework.transaction.interceptor.
    ??? TransactionAttributeSourceAdvisor">

    ??? ???? <property name="transactionInterceptor" ref=
    ??????? "transactionInterceptor"/>

    ??? </bean>

    ??? <!-- 定義額外的Advisor>

    ??? <bean id="customAdvisor" class="lee.MyAdvisor"/>

    </beans>

    DefaultAdvisorAutoProxyCreator支持過(guò)濾和排序。如果需要排序,可讓Advisor實(shí)現(xiàn)org.springframework.core.Ordered接口來(lái)確定順序。TransactionAttributeSourceAdvisor已經(jīng)實(shí)現(xiàn)Ordered接口,因此可配置其順序,默認(rèn)是不排序。

    采用這樣的方式,一樣可以避免客戶端代碼直接訪問(wèn)目標(biāo)Bean,而且配置更加簡(jiǎn)潔。只要目標(biāo)Bean符合切入點(diǎn)檢查,Bean后處理器自動(dòng)為目標(biāo)Bean創(chuàng)建代理,無(wú)須依次指定目標(biāo)Bean名。

    注意:Spring也支持以編程方式創(chuàng)建AOP代理,但這種方式將AOP代理所需要的目標(biāo)對(duì)象和處理Bean等對(duì)象的耦合降低到代碼層次,因此不推薦使用。如果讀者需要深入了解如何通過(guò)編程方式創(chuàng)建AOP代理,請(qǐng)參閱筆者所著的《Spring2.0寶典》。

    posted @ 2009-07-19 10:16 jadmin 閱讀(96) | 評(píng)論 (0)編輯 收藏

    6.2 Spring的AOP

    AOP(Aspect Orient Programming),也就是面向切面編程,作為面向?qū)ο缶幊痰囊环N補(bǔ)充。問(wèn)世的時(shí)間并不太長(zhǎng),甚至在國(guó)內(nèi)的翻譯還不太統(tǒng)一(有些書(shū)翻譯成面向方面編程),但它確實(shí)極好地補(bǔ)充了面向?qū)ο缶幊痰姆绞健C嫦驅(qū)ο缶幊虒⒊绦蚍纸獬筛鱾€(gè)層次的對(duì)象,而面向切面編程將程序運(yùn)行過(guò)程分解成各個(gè)切面。

    可以這樣理解,面向?qū)ο缶幊淌菑撵o態(tài)角度考慮程序結(jié)構(gòu),面向切面編程是從動(dòng)態(tài)角度考慮程序運(yùn)行過(guò)程。

    Spring AOP是Spring框架的一個(gè)重要組件,極好地補(bǔ)充了Spring IoC容器的功能。Spring AOP將Spring IoC容器與AOP組件緊密結(jié)合,豐富了IoC容器的功能。當(dāng)然,即使不使用AOP組件,依然可以使用Spring的IoC容器。

    6.2.1 AOP的基本概念

    AOP從程序運(yùn)行角度考慮程序的流程,提取業(yè)務(wù)處理過(guò)程的切面。AOP面向的是程序運(yùn)行中各個(gè)步驟,希望以更好的方式來(lái)組合業(yè)務(wù)處理的各個(gè)步驟。

    AOP框架并不與特定的代碼耦合,AOP框架能處理程序執(zhí)行中的特定點(diǎn),而不是某個(gè)具體的程序。AOP框架具有如下兩個(gè)特征:

    ?? ● 各步驟之間的良好隔離性。

    ?? ● 源代碼無(wú)關(guān)性。

    下面是關(guān)于面向切面編程的一些術(shù)語(yǔ):

    ?? ● 切面,業(yè)務(wù)流程運(yùn)行的某個(gè)特定步驟,就是運(yùn)行過(guò)程的關(guān)注點(diǎn),關(guān)注點(diǎn)可能橫切多個(gè)對(duì)象。

    ?? ● 連接點(diǎn),程序執(zhí)行過(guò)程中明確的點(diǎn),如方法的調(diào)用或異常的拋出。Spring AOP中,連接點(diǎn)總是方法的調(diào)用,Spring并沒(méi)有顯式地使用連接點(diǎn)。

    ?? ● 處理(Advice),AOP框架在特定的連接點(diǎn)執(zhí)行的動(dòng)作。處理有around、before和throws等類型。大部分框架都以攔截器作為處理模型。

    ?? ● 切入點(diǎn),系列連接點(diǎn)的集合,它確定處理觸發(fā)的時(shí)機(jī)。AOP框架允許開(kāi)發(fā)者自己定義切入點(diǎn),如使用正則表達(dá)式。

    ?? ● 引入,添加方法或字段到被處理的類。Spring允許引入新的接口到任何被處理的對(duì)象。例如,可以使用一個(gè)引入,使任何對(duì)象實(shí)現(xiàn)IsModified接口,以此來(lái)簡(jiǎn)化緩存。

    ?? ● 目標(biāo)對(duì)象,包含連接點(diǎn)的對(duì)象。也稱為被處理對(duì)象或被代理對(duì)象。

    ?? ● AOP代理,AOP框架創(chuàng)建的對(duì)象,包含處理。簡(jiǎn)單地說(shuō),代理就是對(duì)目標(biāo)對(duì)象的加強(qiáng)。Spring中的AOP代理可以是JDK動(dòng)態(tài)代理,也可以是CGLIB代理。前者為實(shí)現(xiàn)接口的目標(biāo)對(duì)象的代理,后者為不實(shí)現(xiàn)接口的目標(biāo)對(duì)象的代理。

    注意:面向切面編程是比較前沿的知識(shí),而國(guó)內(nèi)大部分翻譯人士翻譯計(jì)算機(jī)文獻(xiàn)時(shí),總是一邊開(kāi)著各種詞典和翻譯軟件,一邊逐詞去看文獻(xiàn),不是先從總體上把握知識(shí)的架構(gòu)。因此,難免導(dǎo)致一些術(shù)語(yǔ)的翻譯詞不達(dá)意,例如,Socket被翻譯成“套接字”等。在面向切面編程的各術(shù)語(yǔ)翻譯上,也存在較大的差異。對(duì)于Advice一詞,有翻譯為“通知”的,有翻譯為“建議”的,如此種種,不一而足。實(shí)際上,Advice指AOP框架在特定切面所做的事情,故而筆者翻譯為“處理”,希望可以表達(dá)Advice的真正含義。

    6.2.2 AOP的代理

    所謂AOP代理,就是AOP框架動(dòng)態(tài)創(chuàng)建的對(duì)象,這個(gè)對(duì)象通常可以作為目標(biāo)對(duì)象的替代品,而AOP代理提供比目標(biāo)對(duì)象更加強(qiáng)大的功能。真實(shí)的情形是,當(dāng)應(yīng)用調(diào)用AOP代理的方法時(shí),AOP代理會(huì)在自己的方法中回調(diào)目標(biāo)對(duì)象的方法,從而完成應(yīng)用的調(diào)用。

    關(guān)于AOP代理的典型例子就是Spring中的事務(wù)代理Bean。通常,目標(biāo)Bean的方法不是事務(wù)性的,而AOP代理包含目標(biāo)Bean的全部方法,而且這些方法經(jīng)過(guò)加強(qiáng)變成了事務(wù)性方法。簡(jiǎn)單地說(shuō),目標(biāo)對(duì)象是藍(lán)本,AOP代理是目標(biāo)對(duì)象的加強(qiáng),在目標(biāo)對(duì)象的基礎(chǔ)上,增加屬性和方法,提供更強(qiáng)大的功能。

    目標(biāo)對(duì)象包含一系列切入點(diǎn)。切入點(diǎn)可以觸發(fā)處理連接點(diǎn)集合。用戶可以自己定義切入點(diǎn),如使用正則表達(dá)式。AOP代理包裝目標(biāo)對(duì)象,在切入點(diǎn)處加入處理。在切入點(diǎn)加入的處理,使得目標(biāo)對(duì)象的方法功能更強(qiáng)。

    Spring默認(rèn)使用JDK動(dòng)態(tài)代理實(shí)現(xiàn)AOP代理,主要用于代理接口。也可以使用CGLIB代理。實(shí)現(xiàn)類的代理,而不是接口。如果業(yè)務(wù)對(duì)象沒(méi)有實(shí)現(xiàn)接口,默認(rèn)使用CGLIB代理。但面向接口編程是良好的習(xí)慣,盡量不要面向具體類編程。因此,業(yè)務(wù)對(duì)象通常應(yīng)實(shí)現(xiàn)一個(gè)或多個(gè)接口。

    下面是一個(gè)簡(jiǎn)單動(dòng)態(tài)代理模式的示例,首先有一個(gè)Dog的接口,接口如下:

    public interface Dog

    {

    ??? //info方法聲明

    ??? public void info();

    ??? //run方法聲明

    ??? public void run();

    }

    然后,給出該接口的實(shí)現(xiàn)類,實(shí)現(xiàn)類必須實(shí)現(xiàn)兩個(gè)方法,源代碼如下:

    public class DogImpl implements Dog

    {

    ??? //info方法實(shí)現(xiàn),僅僅打印一個(gè)字符串

    ??? public void info()

    ??? {

    ??????? System.out.println("我是一只獵狗");

    ??? }

    ??? //run方法實(shí)現(xiàn),僅僅打印一個(gè)字符串

    ??? public void run()

    ??? {

    ??????? System.out.println("我奔跑迅速");

    ??? }

    }

    上面的代碼沒(méi)有絲毫獨(dú)特之處,是典型的面向接口編程的模型,為了有更好的解耦,采用工廠來(lái)創(chuàng)建Dog實(shí)例。工廠源代碼如下:

    public class DogFactory

    {

    ??? //工廠本身是單態(tài)模式,因此,將DogFactory作為靜態(tài)成員變量保存

    ??? private static DogFactory df;

    ??? //將Dog實(shí)例緩存

    ??? private Dog gundog;

    ??? //默認(rèn)的構(gòu)造器,單態(tài)模式需要的構(gòu)造器是private

    ??? private DogFactory()

    ??? {

    ??? }

    ??? //單態(tài)模式所需的靜態(tài)方法,該方法是創(chuàng)建本類實(shí)例的唯一方法點(diǎn)

    ??? public static DogFactory instance()

    ??? {

    ??????? if (df == null)

    ??????? {

    ??????????? df = new DogFactory();

    ??????? }

    ??????? return df;

    ??? }

    ??? //獲得Dog實(shí)例

    ??? public Dog getDog(String dogName)

    ??? {

    ??????? //根據(jù)字符串參數(shù)決定返回的實(shí)例

    ??????? if (dogName.equals("gundog"))

    ??????? {

    ??????????? //返回Dog實(shí)例之前,先判斷緩存的Dog是否存在,如果不存在才創(chuàng)建,

    ??????????? 否則直接返回緩存的Dog實(shí)例

    ??????????? if (gundog == null )

    ??????????? {

    ??????????????? gundog = new DogImpl();

    ??????????? }

    ??????????? return gundog;

    ??? ???? }

    ??????? return null;

    ??? }

    }

    下面是一個(gè)通用的處理類,該處理類沒(méi)有與任何特定的類耦合,它可以處理所有的目標(biāo)對(duì)象。從JDK 1.3起,Java的import java.lang.reflect下增加InvocationHandler接口,該接口是所有處理類的根接口。

    該類處理類的源代碼如下:

    public class ProxyHandler implements InvocationHandler

    {

    ??? //需被代理的目標(biāo)對(duì)象

    ??? private Object target;

    ??? //執(zhí)行代理的目標(biāo)方法時(shí),該invoke方法會(huì)被自動(dòng)調(diào)用

    ??? public Object invoke(Object proxy, Method method, Object[] args)throws
    ??? Exception

    ??? {

    ??????? Object result = null;

    ??????? if (method.getName().equals("info"))

    ??????? {

    ??????????? System.out.println("======開(kāi)始事務(wù)...");

    ??????????? result =method.invoke(target, args);

    ??????????? System.out.println("======提交事務(wù)...");

    ??????? }

    ??????? else

    ??????? {

    ??????????? result =method.invoke(target, args);

    ??????? }

    ??????? return result;

    ??? }

    ??? //通過(guò)該方法,設(shè)置目標(biāo)對(duì)象

    ??? public void setTarget(Object o)

    ??? {

    ??????? this.target = o;

    ??? }

    }

    該處理類實(shí)現(xiàn)InvocationHandler接口,實(shí)現(xiàn)該接口必須實(shí)現(xiàn)invoke(Object proxy, Method method, Object[] args)方法,程序調(diào)用代理的目標(biāo)方法時(shí),自動(dòng)變成調(diào)用invoke方法。

    該處理類并未與任何接口或類耦合,它完全是通用的,它的目標(biāo)實(shí)例是Object類型,可以是任何的類型。

    在invoke方法內(nèi),對(duì)目標(biāo)對(duì)象的info方法進(jìn)行簡(jiǎn)單加強(qiáng),在開(kāi)始執(zhí)行目標(biāo)對(duì)象的方法之前,先打印開(kāi)始事務(wù),執(zhí)行目標(biāo)對(duì)象的方法之后,打印提交事務(wù)。

    通過(guò)method對(duì)象的invoke方法,可以完成目標(biāo)對(duì)象的方法調(diào)用,執(zhí)行代碼如下:

    result =method.invoke(target, args);

    下面是代理工廠:

    public class MyProxyFactory

    {

    ??? /**

    ????? * 實(shí)例Service對(duì)象

    ???? * @param serviceName String

    ???? * @return Object

    ???? */

    ??? public static Object getProxy(Object object)

    ??? {

    ?????? //代理的處理類

    ??????? ProxyHandler handler = new ProxyHandler();

    ?????? //把該dog實(shí)例托付給代理操作

    ??????? handler.setTarget(object);

    ??????? //第一個(gè)參數(shù)是用來(lái)創(chuàng)建動(dòng)態(tài)代理的ClassLoader對(duì)象,只要該對(duì)象能訪問(wèn)Dog接口
    ??????? 即可

    ??????? //第二個(gè)參數(shù)是接口數(shù)組,正是代理該接口數(shù)組

    ??????? //第三個(gè)參數(shù)是代理包含的處理實(shí)例

    ??????? return Proxy.newProxyInstance(DogImpl.class.getClassLoader(),

    ??????????? object.getClass().getInterfaces(),handler);

    ??? }

    }

    代理工廠里有一行代碼:

    Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),handler);

    Proxy.newProxyInstance()方法根據(jù)接口數(shù)組動(dòng)態(tài)創(chuàng)建代理類實(shí)例,接口數(shù)組通過(guò)object.getClass().getInterfaces()方法獲得,創(chuàng)建的代理類是JVM在內(nèi)存中動(dòng)態(tài)創(chuàng)建的,該類實(shí)現(xiàn)傳入接口數(shù)組的全部接口。

    因此,Dynamic Proxy要求被代理的必須是接口的實(shí)現(xiàn)類,否則無(wú)法為其構(gòu)造相應(yīng)的動(dòng)態(tài)類。因此,Spring對(duì)接口實(shí)現(xiàn)類采用Dynamic Proxy實(shí)現(xiàn)AOP,而對(duì)沒(méi)有實(shí)現(xiàn)任何接口的類,則通過(guò)CGLIB實(shí)現(xiàn)AOP代理。

    下面是主程序:

    public class TestDog

    {

    ??? public static void main(String[] args)

    ??? {

    ??????? Dog dog = null;

    ??????? //創(chuàng)建Dog實(shí)例,該實(shí)例將作為被代理對(duì)象

    ??????? Dog targetObject = DogFactory.instance().getDog("gundog");

    ??????? //以目標(biāo)對(duì)象創(chuàng)建代理

    ??????? Object proxy = MyProxyFactory.getProxy(targetObject);

    ??????? if (proxy instanceof Dog)

    ??????? {

    ??????????? dog = (Dog)proxy;

    ??????? }

    ??????? //測(cè)試代理的方法

    ??????? dog.info();

    ??????? dog.run();

    ??? }

    }

    代理實(shí)例會(huì)實(shí)現(xiàn)目標(biāo)對(duì)象實(shí)現(xiàn)的全部接口。因此,代理實(shí)例也實(shí)現(xiàn)了Dog接口,程序運(yùn)行結(jié)果如下:

    [java] ======開(kāi)始事務(wù)...

    [java] 我是一只獵狗

    [java] ======提交事務(wù)...

    [java] 我奔跑迅速

    代理實(shí)例加強(qiáng)了目標(biāo)對(duì)象的方法——僅僅打印了兩行字符串。當(dāng)然,此種加強(qiáng)沒(méi)有實(shí)際意義。試想一下,若程序中打印字符串的地方,換成真實(shí)的事務(wù)開(kāi)始和事務(wù)提交,則代理實(shí)例的方法為目標(biāo)對(duì)象的方法增加了事務(wù)性。

    6.2.3 創(chuàng)建AOP代理

    通過(guò)前面的介紹,AOP代理就是由AOP框架動(dòng)態(tài)生成的一個(gè)對(duì)象,該對(duì)象可作為目標(biāo)對(duì)象使用,AOP代理包含了目標(biāo)對(duì)象的全部方法。但AOP代理中的方法與目標(biāo)對(duì)象的方法存在差異:AOP方法在特定切面插入處理,在處理之間回調(diào)目標(biāo)對(duì)象的方法。

    AOP代理所包含的方法與目標(biāo)對(duì)象所包含的方法的示意圖,如圖6.1所示。

    文本框:圖6.1  AOP代理的方法與目標(biāo)對(duì)象的方法Spring中AOP代理由Spring的IoC容器負(fù)責(zé)生成和管理,其依賴關(guān)系也由IoC容器負(fù)責(zé)管理。因此,AOP代理能夠引用容器中的其他Bean實(shí)例,這種引用由IoC容器的依賴注入提供。

    Spring的AOP代理大都由ProxyFactoryBean工廠類產(chǎn)生,如圖6.1所示,產(chǎn)生一個(gè)AOP代理至少有兩個(gè)部分:目標(biāo)對(duì)象和AOP框架所加入的處理。因此,配置ProxyFactoryBean時(shí)需要確定如下兩個(gè)屬性:

    ?? ● 代理的目標(biāo)對(duì)象。

    ?? ● 處理(Advice)。

    注意:關(guān)于Advice的更多知識(shí),此處由于篇幅原因,無(wú)法深入討論。實(shí)際上,Spring也提供了很多種Advice的實(shí)現(xiàn),如需要更深入了解Spring AOP,請(qǐng)讀者參考筆者所著的《Spring2.0寶典》。

    所有代理工廠類的父類是org.springframework.aop.framework.ProxyConfig。因此,該類的屬性是所有代理工廠的共同屬性,這些屬性也是非常關(guān)鍵的,包括:

    ?? ● proxyTargetClass,確定是否代理目標(biāo)類,如果需要代理目標(biāo)是類,該屬性設(shè)為true,此時(shí)需要使用CGLIB生成代理;如果代理目標(biāo)是接口,該屬性設(shè)為false,默認(rèn)是false。

    ?? ● optimize,確定是否使用強(qiáng)優(yōu)化來(lái)創(chuàng)建代理。該屬性僅對(duì)CGLIB代理有效;對(duì)JDK 動(dòng)態(tài)代理無(wú)效。

    ?? ● frozen,確定是否禁止改變處理,默認(rèn)是false。

    ?? ● exposeProxy,代理是否可以通過(guò)ThreadLocal訪問(wèn),如果exposeProxy屬性為true,則可通過(guò)AopContext.currentProxy()方法獲得代理。

    ?? ● aopProxyFactory,所使用的AopProxyFactory具體實(shí)現(xiàn)。該參數(shù)用來(lái)指定使用動(dòng)態(tài)代理、CGLIB或其他代理策略。默認(rèn)選擇動(dòng)態(tài)代理或CGLIB。一般不需要指定該屬性,除非需要使用新的代理類型,才指定該屬性。

    配置ProxyFactoryBean工廠bean時(shí),還需要指定它的特定屬性,ProxyFactoryBean的特定屬性如下所示:

    ?? ● proxyInterfaces,接口名的字符串?dāng)?shù)組。如果沒(méi)有確定該參數(shù),默認(rèn)使用CGLIB代理。

    ?? ● interceptorNames,處理名的字符串?dāng)?shù)組。此處的次序很重要,排在前面的處理,優(yōu)先被調(diào)用。此處的處理名,只能是當(dāng)前工廠中處理的名稱,而不能使用bean引用。處理名字支持使用通配符(*)。

    ?? ● singleton,工廠是否返回單態(tài)代理。默認(rèn)是true,無(wú)論 getObject()被調(diào)用多少次,將返回相同的代理實(shí)例。如果需要使用有狀態(tài)的處理——例如,有狀態(tài)的mixin,可改變默認(rèn)設(shè)置,prototype處理。

    posted @ 2009-07-19 10:15 jadmin 閱讀(72) | 評(píng)論 (0)編輯 收藏

    6.1 兩種后處理器

    Spring 框架提供了很好的擴(kuò)展性,除了可以與各種第三方框架良好整合外,其IoC容器也允許開(kāi)發(fā)者進(jìn)行擴(kuò)展。這種擴(kuò)展并不是通過(guò)實(shí)現(xiàn)BeanFactory或ApplicationContext的子類,而是通過(guò)兩個(gè)后處理器對(duì)IoC容器進(jìn)行擴(kuò)展。Spring提供了兩種常用的后處理器:

    ?? ● Bean后處理器,這種后處理器會(huì)對(duì)容器中特定的Bean進(jìn)行定制,例如功能的??? 加強(qiáng)。

    ?? ● 容器后處理器,這種后處理器對(duì)IoC容器進(jìn)行特定的后處理。

    下面將介紹這兩種常用的后處理器以及兩種后處理器相關(guān)知識(shí)。

    6.1.1 Bean后處理器

    Bean后處理器是一種特殊的Bean,這種特殊的Bean并不對(duì)外提供服務(wù),它無(wú)須id屬性,但它負(fù)責(zé)對(duì)容器中的其他Bean執(zhí)行后處理,例如為容器中的目標(biāo)Bean生成代理。這種Bean可稱為Bean后處理器,它在Bean實(shí)例創(chuàng)建成功后,對(duì)其進(jìn)行進(jìn)一步的加強(qiáng)???? 處理。

    Bean后處理器必須實(shí)現(xiàn)BeanPostProcessor接口。

    BeanPostProcessor接口包含兩個(gè)方法:

    ?? ● Object postProcessBeforeInitialization(Object bean, String name)throws BeansExce- ption,該方法的第一個(gè)參數(shù)是系統(tǒng)即將初始化的Bean實(shí)例,第二個(gè)參數(shù)是Bean實(shí)例的名字。

    ?? ● Object postProcessAfterInitialization(Object bean, String name)throws BeansExce- ption,該方法的第一個(gè)參數(shù)是系統(tǒng)剛完成初始化的Bean實(shí)例,第二個(gè)參數(shù)是Bean實(shí)例的名字。

    實(shí)現(xiàn)該接口的Bean必須實(shí)現(xiàn)這兩個(gè)方法,這兩個(gè)方法會(huì)對(duì)容器的Bean進(jìn)行后處理。兩個(gè)方法會(huì)在目標(biāo)Bean初始化之前和初始化之后分別調(diào)用。這兩個(gè)方法用于對(duì)系統(tǒng)完成的默認(rèn)初始化進(jìn)行加強(qiáng)。

    注意:Bean后處理器是對(duì)IoC容器一種極好的擴(kuò)展,Bean后處理器可以對(duì)容器中的Bean進(jìn)行后處理,這種后處理完全由開(kāi)發(fā)者決定。

    下面將定義一個(gè)簡(jiǎn)單的Bean后處理器,該Bean后處理器將對(duì)容器中其他Bean進(jìn)行后處理。Bean后處理器的代碼如下:

    //自定義Bean后處理器,負(fù)責(zé)后處理容器中所有的Bean

    public class MyBeanPostProcessor implements BeanPostProcessor

    {

    ??? //在初始化bean之前,調(diào)用該方法

    ??? public Object postProcessBeforeInitialization(Object bean , String
    ??? beanName)throws BeansException

    ??? {

    ??????? //僅僅打印一行字符串

    ??????? System.out.println("系統(tǒng)正在準(zhǔn)備對(duì)" + beanName + "進(jìn)行初始化...");

    ??????? return bean;

    ??? }

    ??? //在初始化bean之后,調(diào)用該方法

    ??? public Object postProcessAfterInitialization(Object bean , String
    ??? beanName)throws BeansException

    ??? {

    ??????? System.out.println("系統(tǒng)已經(jīng)完成對(duì)" + beanName + "的初始化");

    ??????? //如果系統(tǒng)剛完成初始化的bean是Chinese

    ??????? if (bean instanceof Chinese)

    ??????? {

    ??????????? //為Chinese實(shí)例設(shè)置name屬性

    ??????????? Chinese c = (Chinese)bean;

    ??????????? c.setName("wawa");

    ????? ?? }

    ??????? return bean;

    ??? }

    }

    下面是Chinese的源代碼,該類實(shí)現(xiàn)了InitializingBean接口,還額外提供了一個(gè)初始化方法,這兩個(gè)方法都由Spring容器控制回調(diào)。

    public class Chinese implements Person,InitializingBean

    {

    ??? private Axe axe;

    ??? private String name;

    ??? public Chinese()

    ??? {

    ??????? System.out.println("Spring實(shí)例化主調(diào)bean:Chinese實(shí)例...");

    ??? }

    ??? public void setAxe(Axe axe)

    ??? {

    ??????? System.out.println("Spring執(zhí)行依賴關(guān)系注入...");

    ??????? this.axe = axe;

    ??? }

    ??? public void setName(String name)

    ??? {

    ??????? this.name = name;

    ??? }

    ??? public void useAxe()

    ??? {

    ??????? System.out.println(name + axe.chop());

    ??? }

    ??? public void init()

    ??? {

    ??????? System.out.println("正在執(zhí)行初始化方法?? init...");

    ??? }

    ?? public void afterPropertiesSet() throws Exception

    ??? {

    ?????? System.out.println("正在執(zhí)行初始化方法 afterPropertiesSet...");

    ??? }

    }

    配置文件如下:

    <?xml version="1.0" encoding="gb2312"?>

    <!-- 指定Spring 配置文件的dtd>

    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

    ??? "http://www.springframework.org/dtd/spring-beans.dtd">

    <!-- Spring配置文件的根元素 -->

    <beans>

    ??? <!-- 配置bean后處理器,可以沒(méi)有id屬性,此處id屬性為了后面引用 -->

    ??? <bean id="beanPostProcessor" class="lee.MyBeanPostProcessor"/>

    ??? <bean id="steelAxe" class="lee.SteelAxe"/>

    ??? <bean id="chinese" class="lee.Chinese" init-method="init">

    ??????? <property name="axe" ref="steelAxe"/>

    ??? </bean>

    </beans>

    本應(yīng)用的chinese具有兩個(gè)初始化方法:

    ?? ● init-method指定初始化方法。

    ?? ● 實(shí)現(xiàn)InitializingBean接口,提供了afterPropertiesSet初始化方法。

    MyBeanPostProcessor類實(shí)現(xiàn)了BeanPostProcessor接口,并實(shí)現(xiàn)了該接口的兩個(gè)方法,這兩個(gè)方法分別在初始化方法調(diào)用之前和之后得到回調(diào)。

    注意:上面的配置文件配置Bean后處理器時(shí),依然為Bean處理器指定了id屬性,指定id屬性是為了方便程序通過(guò)該id屬性訪問(wèn)Bean后處理器。大部分時(shí)候,程序無(wú)須手動(dòng)訪問(wèn)該Bean后處理器,因此無(wú)須為其指定id屬性。

    主程序如下:

    public class BeanTest

    {

    ??? public static void main(String[] args)throws Exception

    ??? {

    ??????? //CLASSPATH路徑下的bean.xml文件創(chuàng)建Resource對(duì)象

    ??????? ClassPathResource isr = new ClassPathResource("bean.xml");

    ??????? //以Resource對(duì)象作為參數(shù),創(chuàng)建BeanFactory的實(shí)例

    ??????? XmlBeanFactory factory = new XmlBeanFactory(isr);

    ??????? //獲取Bean后處理器實(shí)例

    ??????? MyBeanPostProcessor beanProcessor =

    ??????????? (MyBeanPostProcessor)factory.getBean("beanPostProcessor");

    ??????? //注冊(cè)BeanPostProcessor實(shí)例

    ??????? factory.addBeanPostProcessor(beanProcessor);

    ??????? System.out.println("程序已經(jīng)實(shí)例化BeanFactory...");

    ??????? Person p = (Person)factory.getBean("chinese");

    ??????? System.out.println("程序中已經(jīng)完成了chinese bean的實(shí)例化...");

    ??????? p.useAxe();

    ??? }

    }

    如果使用BeanFactory作為Spring容器,必須手動(dòng)注冊(cè)Bean后處理器,因此在程序中先獲取Bean后處理器實(shí)例,然后手動(dòng)注冊(cè)——這就是在配置文件中指定Bean后處理器id屬性的原因。通過(guò)BeanFactory的addBeanPostProcessor可以注冊(cè)BeanPostProcessor實(shí)例。程序執(zhí)行結(jié)果如下:

    [java] 程序已經(jīng)實(shí)例化BeanFactory...

    [java] Spring實(shí)例化主調(diào)bean:Chinese實(shí)例...

    [java] Spring實(shí)例化依賴bean:SteelAxe實(shí)例...

    [java] 系統(tǒng)正在準(zhǔn)備對(duì)steelAxe進(jìn)行初始化...

    [java] 系統(tǒng)已經(jīng)完成對(duì)steelAxe的初始化

    [java] Spring執(zhí)行依賴關(guān)系注入...

    [java] 系統(tǒng)正在準(zhǔn)備對(duì)chinese進(jìn)行初始化...

    [java] 正在執(zhí)行初始化方法 afterPropertiesSet...

    [java] 正在執(zhí)行初始化方法?? init...

    [java] 系統(tǒng)已經(jīng)完成對(duì)chinese的初始化

    [java] 程序中已經(jīng)完成了chinese bean的實(shí)例化...

    [java] wawa鋼斧砍柴真快

    在配置文件中配置chinese實(shí)例時(shí),并未指定name屬性值。但程序執(zhí)行時(shí),name屬性有了值,這就是Bean后處理器完成的,在Bean后處理器中判斷Bean是否是Chinese實(shí)例,然后設(shè)置它的name屬性。

    容器中一旦注冊(cè)了Bean后處理器,Bean后處理器會(huì)自動(dòng)啟動(dòng),在容器中每個(gè)Bean創(chuàng)建時(shí)自動(dòng)工作,完成加入Bean后處理器需要完成的工作。

    實(shí)現(xiàn)BeanPostProcessor接口的Bean后處理器可對(duì)Bean進(jìn)行任何操作,包括完全忽略這個(gè)回調(diào)。BeanPostProcessor通常用來(lái)檢查標(biāo)記接口或?qū)ean包裝成一個(gè)Proxy的事情。Spring的很多工具類,就是通過(guò)Bean后處理器完成的。

    從主程序中看到,采用BeanFactory作為Spring容器時(shí),必須手動(dòng)注冊(cè)BeanPost- Processor。而對(duì)于ApplicationContext,則無(wú)須手動(dòng)注冊(cè)。ApplicationContext可自動(dòng)檢測(cè)到容器中的Bean后處理器,自動(dòng)注冊(cè)。Bean后處理器會(huì)在Bean實(shí)例創(chuàng)建時(shí),自動(dòng)啟動(dòng)。即主程序采用如下代碼,效果完全一樣:

    public class BeanTest

    {

    ??? public static void main(String[] args)throws Exception

    ??? {

    ??????? ApplicationContext ctx = new ClassPathXmlApplicationContext
    ??????? ("bean.xml");

    ??????? Person p = (Person)factory.getBean("chinese");

    ??????? System.out.println("程序中已經(jīng)完成了chinese bean的實(shí)例化...");

    ??????? p.useAxe();

    ??? }

    }

    使用ApplicationContext作為容器,無(wú)須手動(dòng)注冊(cè)BeanPostProcessor。因此,如果需要使用Bean后處理器,Spring容器建議使用ApplicationContext,而不是BeanFactory。

    6.1.2 Bean后處理器的用處

    上一節(jié)介紹了一個(gè)簡(jiǎn)單的Bean后處理器,上面的Bean后處理器負(fù)責(zé)對(duì)容器中的Chinese Bean進(jìn)行后處理,不管Chinese Bean如何初始化,總是將Chinese Bean的name屬性設(shè)置為wawa。這種后處理看起來(lái)作用并不是特別大。

    實(shí)際上,Bean后處理器完成的工作更加實(shí)際,例如生成Proxy。Spring框架本身提供了大量的Bean后處理器,這些后處理器負(fù)責(zé)對(duì)容器中的Bean進(jìn)行后處理。

    下面是Spring提供的兩個(gè)常用的后處理器:

    ?? ● BeanNameAutoProxyCreator,根據(jù)Bean實(shí)例的name屬性,創(chuàng)建Bean實(shí)例的代理。

    ?? ● DefaultAdvisorAutoProxyCreator,根據(jù)提供的Advisor,對(duì)容器中所有的Bean實(shí)例創(chuàng)建代理。

    上面提供的兩個(gè)Bean后處理器,都用于根據(jù)容器中配置的攔截器創(chuàng)建目標(biāo)Bean代理,目標(biāo)代理就在目標(biāo)Bean的基礎(chǔ)上修改得到。

    注意:如果需要對(duì)容器中某一批Bean進(jìn)行特定的處理,可以考慮使用Bean后處理器。

    6.1.3 容器后處理器

    除了上面提供的Bean后處理器外,Spring還提供了一種容器后處理器。Bean后處理器負(fù)責(zé)后處理容器生成的所有Bean,而容器后處理器則負(fù)責(zé)后處理容器本身。

    容器后處理器必須實(shí)現(xiàn)BeanFactoryPostProcessor接口。實(shí)現(xiàn)該接口必須實(shí)現(xiàn)如下一個(gè)方法:

    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

    實(shí)現(xiàn)該方法的方法體就是對(duì)Spring容器進(jìn)行的處理,這種處理可以對(duì)Spring容器進(jìn)行任意的擴(kuò)展,當(dāng)然也可以對(duì)Spring容器不進(jìn)行任何處理。

    類似于BeanPostProcessor,ApplicationContext可自動(dòng)檢測(cè)到容器中的容器后處理器,并且自動(dòng)注冊(cè)容器后處理器。但若使用BeanFactory作為Spring容器,則必須手動(dòng)注冊(cè)后處理器。

    下面定義了一個(gè)容器后處理器,這個(gè)容器后處理器實(shí)現(xiàn)BeanFactoryPostProcessor接口,但并未對(duì)Spring容器進(jìn)行任何處理,只是打印出一行簡(jiǎn)單的信息。該容器后處理器的代碼如下:

    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor

    {

    ??? //容器后處理器對(duì)容器進(jìn)行的處理在該方法中實(shí)現(xiàn)

    ??? public void postProcessBeanFactory(ConfigurableListableBeanFactory
    ??? beanFactory)

    ??????? throws BeansException

    ??? {

    ??????? System.out.println("程序?qū)pring所做的BeanFactory的初始化沒(méi)有意
    ??????? 見(jiàn)...");

    ??? }

    }

    將該Bean作為普通Bean部署在容器中,然后使用ApplicationContext作為容器,容器會(huì)自動(dòng)調(diào)用BeanFactoryPostProcessor處理Spring容器。程序執(zhí)行效果如下:

    [java] 程序?qū)pring所做的BeanFactory的初始化沒(méi)有意見(jiàn)...

    實(shí)現(xiàn)BeanFactoryPostProcessor接口的Bean后處理器不僅可對(duì)BeanFactory執(zhí)行后處理,也可以對(duì)ApplicationContext容器執(zhí)行后處理。容器后處理器還可用來(lái)注冊(cè)額外的屬性編輯器。

    注意:Spring沒(méi)有提供ApplicationContextPostProcessor。也就是說(shuō),對(duì)于Application- Context容器,一樣使用BeanFactoryPostProcessor作為容器后處理器。

    Spring已提供如下兩個(gè)常用的容器后處理器,包括:

    ?? ● PropertyResourceConfigurer,屬性占位符配置器。

    ?? ● PropertyPlaceHolderConfigurer,另一種屬性占位符配置器。

    下面將詳細(xì)介紹這兩種常用的容器后處理器。

    6.1.4 屬性占位符配置器

    Spring提供了PropertyPlaceholderConfigurer,它是一個(gè)容器后處理器,負(fù)責(zé)讀取Java屬性文件里的屬性值,并將這些屬性值設(shè)置到Spring容器定義中。

    通過(guò)使用PropertyPlaceholderConfigurer后處理器,可以將Spring配置文件中的部分設(shè)置放在屬性文件中設(shè)置。這種配置方式當(dāng)然有其優(yōu)勢(shì):可以將部分相似的配置(如數(shù)據(jù)庫(kù)的urls、用戶名和密碼)放在特定的屬性文件中,如果只需要修改這部分配置,則無(wú)須修改Spring配置文件,修改屬性文件即可。

    下面的配置文件配置了PropertyPlaceholderConfigurer后處理器,在配置數(shù)據(jù)源Bean時(shí),使用了屬性文件中的屬性值。配置文件的代碼如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 配置一個(gè)容器后處理器Bean -->

    ??? <bean id="propertyConfigurer"

    ??????? class="org.springframework.beans.factory.config.
    ??????? PropertyPlaceholderConfigurer">

    ??????? <!-- locations屬性指定屬性文件的位置 -->

    ??????? <property name="locations">

    ??????????? <list>

    ??????????????? <value>dbconn.properties</value>

    ??????????????? <!-- 如果有多個(gè)屬性文件,依次在下面列出來(lái) -->

    ??????????? </list>

    ??????? </property>

    ??? </bean>

    ??? <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->

    ??? <bean id="dataSource" class="com.mchange.v2.c3p0.
    ??? ComboPooledDataSource" destroy-method="close">

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->

    ??????? <property name="driverClass" value="${jdbc.driverClassName}"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->

    ??????? <property name="jdbcUrl" value="${jdbc.url}"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶名 -->

    ??????? <property name="user" value="${jdbc.username}"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->

    ??????? <property name="password" value="${jdbc.password}"/>

    ??? </bean>

    </beans>

    在上面的配置文件中,配置driverClass和jdbcUrl等信息時(shí),并未直接設(shè)置這些屬性的屬性值,而是設(shè)置了${jdbc.driverClassName}和${jdbc.url}屬性值。這表明Spring容器將從propertyConfigurer指定屬性文件中搜索這些key對(duì)應(yīng)的value,并為該Bean的屬性值設(shè)置這些value值。

    如前所述,ApplicationContext會(huì)自動(dòng)檢測(cè)部署在容器的容器后處理器,無(wú)須額外的注冊(cè),容器自動(dòng)注冊(cè)。因此,只需提供如下Java Properties文件:

    jdbc.driverClassName=com.mysql.jdbc.Driver

    jdbc.url=jdbc:mysql://localhost:3306/j2ee

    jdbc.username=root

    jdbc.password=32147

    通過(guò)這種方法,可從主XML配置文件中分離出部分配置信息。如果僅需要修改數(shù)據(jù)庫(kù)連接屬性,則無(wú)須修改主XML配置文件,只需要修改屬性文件即可。采用屬性占位符的配置方式,可以支持使用多個(gè)屬性文件。通過(guò)這種方式,可將配置文件分割成多個(gè)屬性文件,從而降低修改配置的風(fēng)險(xiǎn)。

    注意:對(duì)于數(shù)據(jù)庫(kù)連接等信息集中的配置,可以將其配置在Java屬性文件中,但不要過(guò)多地將Spring配置信息抽離到Java屬性文件中,否則可能會(huì)降低Spring配置文件的可讀性。

    6.1.5 另一種屬性占位符配置器(PropertyOverrideConfigurer)

    PropertyOverrideConfigurer是Spring提供的另一個(gè)容器后處理器,這個(gè)后處理器的額作用與上面介紹的容器后處理器作用大致相同。但也存在些許差別:PropertyOverride- Configurer使用的屬性文件用于覆蓋XML配置文件中的定義。即PropertyOverride- Configurer允許XML配置文件中有默認(rèn)的配置信息。

    如果PropertyOverrideConfigurer的屬性文件有對(duì)應(yīng)配置信息,XML文件中的配置信息被覆蓋;否則,直接使用XML文件中的配置信息。使用PropertyOverrideConfigurer的屬性文件,應(yīng)是如下的格式:

    beanName.property=value

    beanName是屬性占位符試圖覆蓋的Bean名,property是試圖覆蓋的屬性名。看如下配置文件:

    <?xml version="1.0" encoding="GBK"?>

    <!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans"

    ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    ??? <!-- 配置一個(gè)屬性占位符Bean。ApplictionContext能自動(dòng)識(shí)別
    ??? PropertyPlaceholderConfigurer Bean -->

    ??? <bean id="propertyOverrider"

    ??????? class="org.springframework.beans.factory.config.
    ??????? PropertyOverrideConfigurer">

    ??????? <property name="locations">

    ??????????? <list>

    ??? ??????????? <value>dbconn.properties</value>

    ??????????????? <!-- 如果有多個(gè)屬性文件,依次在下面列出來(lái) -->

    ??????????? </list>

    ??????? </property>

    ??? </bean>

    ??? <!-- 定義數(shù)據(jù)源Bean,使用C3P0數(shù)據(jù)源實(shí)現(xiàn) -->

    ??? <bean id="dataSource" class="com.mchange.v2.c3p0.
    ??? ComboPooledDataSource" destroy-method="close">

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng) -->

    ??????? <property name="driverClass" value="dd"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的URL -->

    ??????? <property name="jdbcUrl" value="xx"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的用戶名 -->

    ??????? <property name="user" value="dd"/>

    ??????? <!-- 指定連接數(shù)據(jù)庫(kù)的密碼 -->

    ??????? <property name="password" value="xx"/>

    ??? </bean>

    </beans>

    上面的配置文件中,指定數(shù)據(jù)源Bean的各種屬性值時(shí),只是隨意指定了幾個(gè)屬性值,很明顯通過(guò)這幾個(gè)屬性值無(wú)法連接到數(shù)據(jù)庫(kù)服務(wù)。

    但因?yàn)镾pring容器中部署了一個(gè)PropertyOverrideConfigurer的容器后處理器,而且Spring容器使用ApplicationContext作為容器,它會(huì)自動(dòng)檢測(cè)容器中的容器后處理器,無(wú)須額外的注冊(cè),容器自動(dòng)注冊(cè)該后處理器。

    PropertyOverrideConfigurer后處理器讀取dbconn.properties文件中的屬性,用于覆蓋目標(biāo)Bean的屬性。因此,如果屬性文件中有dataSource Bean屬性的設(shè)置,則配置文件中指定的屬性值將沒(méi)有任何作用。

    dbconn.properties屬性文件如下:

    dataSource.driverClassName=com.mysql.jdbc.Driver

    dataSource.url=jdbc:mysql://wonder:3306/j2ee

    dataSource.username=root

    dataSource.password=32147

    注意屬性文件的格式必須是:

    beanName.property=value

    也就是說(shuō),dataSource必須是容器中真實(shí)存在的bean名,否則程序?qū)⒊鲥e(cuò)。

    注意:程序無(wú)法知道BeanFactory定義是否被覆蓋。僅僅通過(guò)察看XML配置文件,無(wú)法知道配置文件的配置信息是否被覆蓋。如有多個(gè)PorpertyOverrideConfigurer對(duì)同一Bean屬性定義了覆蓋,最后一個(gè)覆蓋獲勝。

    posted @ 2009-07-19 10:12 jadmin 閱讀(124) | 評(píng)論 (0)編輯 收藏
    僅列出標(biāo)題
    共50頁(yè): First 上一頁(yè) 12 13 14 15 16 17 18 19 20 下一頁(yè) Last 
    主站蜘蛛池模板: 爱情岛论坛网亚洲品质自拍| 免费福利在线播放| 亚洲综合精品网站在线观看| 亚洲日韩在线中文字幕综合| 女人18毛片水真多免费播放| 亚洲国产一区二区三区在线观看 | 亚欧洲精品在线视频免费观看| 国产精品久久久久影院免费| 亚洲av日韩专区在线观看| 国产在线观看免费完整版中文版| 亚洲av乱码中文一区二区三区 | 久久不见久久见中文字幕免费| 亚洲一区二区三区不卡在线播放| 99在线视频免费观看视频 | 成人免费777777被爆出| 亚洲色婷婷六月亚洲婷婷6月| 中文字幕永久免费视频| 久久99国产亚洲精品观看| 日本不卡免费新一区二区三区| 亚洲伊人tv综合网色| 美女被cao免费看在线看网站| 国产99在线|亚洲| 日韩在线看片免费人成视频播放| 男女超爽视频免费播放| 国产精品亚洲玖玖玖在线观看| 97在线视频免费公开视频| 亚洲狠狠久久综合一区77777| AV无码免费永久在线观看| 亚洲精品理论电影在线观看| 亚洲第一视频在线观看免费| 很黄很污的网站免费| 波多野结衣亚洲一级| mm1313亚洲精品国产| 免费福利电影在线观看| 亚洲av永久无码精品天堂久久 | 亚洲视频一区在线| 日本高清免费网站| 免费久久人人爽人人爽av | 亚洲三级在线播放| 亚洲美女在线国产| 美丽的姑娘免费观看在线播放|