?????? 數據庫的事務處理是在進行數據庫應用開發中必須進行處理的一個問題。那么對于選擇Hibernate作為持久層組件,了解Hibernate的事務處理機制就顯得尤為重要了。
事務的基本概念
?????? 事務(Transaction)是并發控制的基本單位。所謂的事務,它是一個操作序列,這些操作要么都執行,要么都不執行,它是一個不可分割的工作單位。例如,銀行轉賬工作:從一個賬號扣款并使另一個賬號增款,這兩個操作要么都執行,要么都不執行。所以,應該把它們看成一個事務。事務是數據庫維護數據一致性的單位,在每個事務結束時,都能保持數據一致性。
?????? 針對上面的描述可以看出,事務的提出主要是為了解決并發情況下保持數據一致性的問題。
?????? 事務具有以下4個基本特征。
●?? Atomic(原子性):事務中包含的操作被看做一個邏輯單元,這個邏輯單元中的操作要么全部成功,要么全部失敗。
●?? Consistency(一致性):只有合法的數據可以被寫入數據庫,否則事務應該將其回滾到最初狀態。
●?? Isolation(隔離性):事務允許多個用戶對同一個數據進行并發訪問,而不破壞數據的正確性和完整性。同時,并行事務的修改必須與其他并行事務的修改相互獨立。
●?? Durability(持久性):事務結束后,事務處理的結果必須能夠得到固化。
?????? 數據庫肯定是要被廣大客戶所共享訪問的,那么在數據庫操作過程中很可能出現以下幾種不確定情況。
●?? 更新丟失(Lost update):兩個事務都同時更新一行數據,但是第二個事務卻中途失敗退出,導致對數據的兩個修改都失效了。這是因為系統沒有執行任何的鎖操作,因此并發事務并沒有被隔離開來。
●?? 臟讀取(Dirty Reads):一個事務開始讀取了某行數據,但是另外一個事務已經更新了此數據但沒有能夠及時提交。這是相當危險的,因為很可能所有的操作都被回滾。
●?? 不可重復讀取(Non-repeatable Reads):一個事務對同一行數據重復讀取兩次,但是卻得到了不同的結果。例如,在兩次讀取的中途,有另外一個事務對該行數據進行了修改,并提交。
●?? 兩次更新問題(Second lost updates problem):無法重復讀取的特例。有兩個并發事務同時讀取同一行數據,然后其中一個對它進行修改提交,而另一個也進行了修改提交。這就會造成第一次寫操作失效。
●?? 虛讀(Phantom Reads):事務在操作過程中進行兩次查詢,第二次查詢的結果包含了第一次查詢中未出現的數據(這里并不要求兩次查詢的SQL語句相同)。這是因為在兩次查詢過程中有另外一個事務插入數據造成的。
posted @
2009-07-19 21:04 jadmin 閱讀(152) |
評論 (0) |
編輯 收藏
6.5.4 使用HibernateCallBack
HibernateTemplate還提供了一種更加靈活的方式來操作數據庫,通過這種方式可以完全使用Hibernate的操作方式。HibernateTemplate的靈活訪問方式可通過如下兩個方法完成:
?? ● Object execute(HibernateCallback action)。
?? ● List execute(HibernateCallback action)。
這兩個方法都需要一個HibernateCallback的實例,HibernateCallback實例可在任何有效的Hibernate數據訪問中使用。程序開發者通過HibernateCallback,可以完全使用Hibernate靈活的方式來訪問數據庫,解決Spring封裝Hibernate后靈活性不足的缺陷。
HibernateCallback是一個接口,該接口包含一個方法doInHibernate(org.hibernate. Session session),該方法只有一個參數Session。在開發中提供HibernateCallback實現類時,必須實現接口里包含的doInHibernate方法,在該方法體內即可獲得Hibernate Session的引用,一旦獲得了Hibernate Session的引用,就可以完全以Hibernate的方式進行數據庫訪問。
注意:doInHibernate方法內可以訪問Session,該Session對象是綁定在該線程的Session實例。該方法內的持久層操作,與不使用Spring時的持久層操作完全相同。這保證了對于復雜的持久層訪問,依然可以使用Hibernate的訪問方式。
下面的代碼對HibernateDaoSupport類進行擴展(雖然Spring 2.0的HibernateTemplate提供了一個分頁方法setMaxResults,但僅此一個方法依然不能實現分頁查詢),這種擴展主要是為該類增加了3個分頁查詢的方法,分頁查詢時必須直接調用Hibernate的Session完成,因此,必須借助于HibernateCallBack的幫助。
public class YeekuHibernateDaoSupport extends HibernateDaoSupport
{
??? /**
??? * 使用hql 語句進行分頁查詢操作
??? * @param hql 需要查詢的hql語句
??? * @param offset 第一條記錄索引
??? * @param pageSize 每頁需要顯示的記錄數
??? * @return 當前頁的所有記錄
??? */
??? public List findByPage(final String hql,
??????? final int offset, final int pageSize)
??? {
??????? //HibernateDaoSupport已經包含了getHibernateTemplate()方法
??????? List list = getHibernateTemplate().executeFind(new
??????? HibernateCallback()
??????????? {
??????????????? public Object doInHibernate(Session session)
??????????????????? throws HibernateException, SQLException
??????????????? //該方法體內以Hibernate方法進行持久層訪問
??????????????? {
??????????????????? List result = session.createQuery(hql)
??????????????????????????????????????? .setFirstResult(offset)
??????????????????????? ???????????????? .setMaxResults(pageSize)
??????????????????????????????????????? .list();
??????????????????? return result;
??????????????? }
??????????? });
??????? return list;
??? }
??? /**
??? * 使用hql 語句進行分頁查詢操作
??? * @param hql 需要查詢的hql語句
??? * @param value 如果hql有一個參數需要傳入,value就是傳入的參數
??? * @param offset 第一條記錄索引
??? * @param pageSize 每頁需要顯示的記錄數
??? * @return 當前頁的所有記錄
??? */
??? 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
??????????????? {
??????????????????? //下面查詢的是最簡單的Hiberante HQL查詢
??????????????????? List result = session.createQuery(hql)
??????????????????????????????? ???????? .setParameter(0, value)
??????????????????????????????????????? .setFirstResult(offset)
??????????????????????? ???????????????? .setMaxResults(pageSize)
??????????????????? ??????????????????? .list();
??????????????????? return result;
??????????????? }
??????????? });
??????? return list;
??? }
??? /**
??? * 使用hql 語句進行分頁查詢操作
??? * @param hql 需要查詢的hql語句
??? * @param values 如果hql有多個參數需要傳入,values就是傳入的參數數組
??? * @param offset 第一條記錄索引
??? * @param pageSize 每頁需要顯示的記錄數
??? * @return 當前頁的所有記錄
??? */
??? 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;
??? }
}
在上面的代碼實現中,直接使用了getHibernateTemplate()方法,這個方法由Hibernate- DaoSupport提供。而YeekuHibernateDaoSupport是HibernateDaoSupport的子類,因此,可以直接使用該方法。
當實現doInHibernate(Session session)方法時,完全以Hibernate的方式進行數據庫訪問,這樣保證了Hibernate進行數據庫訪問的靈活性。
注意:Spring提供的XxxTemplate和XxxCallBack互為補充,二者體現了Spring框架設計的用心良苦:XxxTemplate對通用操作進行封裝,而XxxCallBack解決了封裝后靈活性不足的缺陷。
6.5.5 實現DAO組件
為了實現DAO組件,Spring提供了大量的XxxDaoSupport類,這些DAO支持類對于實現DAO組件大有幫助,因為這些DAO支持類已經完成了大量基礎性工作。
Spring為Hibernate的DAO提供了工具類HibernateDaoSupport。該類主要提供如下兩個方法以方便DAO的實現:
?? ● public final HibernateTemplate getHibernateTemplate()。
?? ● public final void setSessionFactory(SessionFactory sessionFactory)。
其中,setSessionFactory方法可用于接收Spring的ApplicationContext的依賴注入,可接收配置在Spring的SessionFactory實例,getHibernateTemplate方法用于返回通過SessionFactory產生的HibernateTemplate實例,持久層訪問依然通過HibernateTemplate實例完成。
下面實現的DAO組件繼承了Spring提供的HibernateDaoSupport類,依然實現了PersonDao接口,其功能與前面提供的PersonDao實現類完全相同。其代碼如下:
public class PersonDaoHibernate extends HibernateDaoSupport implements PersonDao
{
??? /**
???? * 加載人實例
???? * @param id 需要加載的Person實例的主鍵值
???? * @return 返回加載的Person實例
???? */
??? public Person get(int id)
??? {
??????? return (Person)getHibernateTemplate().get(Person.class, new
??????? Integer(id));
??? }
??? /**
???? * 保存人實例
???? * @param person 需要保存的Person實例
???? */???
??? public void save(Person person)
??? {
??????? getHibernateTemplate().save(person);
??? }
??? /**
???? * 修改Person實例
???? * @param person 需要修改的Person實例
???? */
??? public void update(Person person)
??? {
??????? getHibernateTemplate().update(person);
??? }
??? /**
???? * 刪除Person實例
???? * @param id 需要刪除的Person的id
???? */
??? public void delete(int id)
??? {
??????? getHibernateTemplate().delete(getHibernateTemplate().
??????? get(Person.class, new Integer(id)));
??? }
??? /**
???? * 刪除Person實例
???? * @param person 需要刪除的Person實例
???? */
??? public void delete(Person person)
??? {
??????? getHibernateTemplate().delete(person);
??? }
??? /**
???? * 根據用戶名查找Person
???? * @param name 用戶名
???? * @return 用戶名對應的全部用戶
???? */
??? public List findByPerson(String name)
??? {
??????? return getHibernateTemplate().find("from Person p where p.name
??????? like ?" , name);???????
??? }
??? /**
??? * 返回全部的Person實例
??? * @return 全部的Person實例
??? */
??? public List findAllPerson()
??? {
??????? return getHibernateTemplate().find("from Person ");
??? }
}
上面的代碼與前面的PersonDAOImpl對比會發現,代碼量大大減少。事實上,DAO的實現依然借助于HibernateTemplate的模板訪問方式,只是HibernateDaoSupport將依賴注入SessionFactory的工作已經完成,獲取HibernateTemplate的工作也已完成。該DAO的配置必須依賴于SessionFactory,配置文件與前面部署DAO組件的方式完全相同,此處不再贅述。
在繼承HibernateDaoSupport的DAO實現里,Hibernate Session的管理完全不需要打開代碼,而由Spring來管理。Spring會根據實際的操作,采用“每次事務打開一次session”的策略,自動提高數據庫訪問的性能。
6.5.6 使用IoC容器組裝各種組件
至此為止,J2EE應用所需要的各種組件都已經出現了,從MVC層的控制器組件,到業務邏輯組件,以及持久層的DAO組件,已經全部成功實現。應用程序代碼并未將這些組件耦合在一起,代碼中都是面向接口編程,因此必須利用Spring的IoC容器將他們組合在一起。
從用戶角度來看,用戶發出HTTP請求,當MVC框架的控制器組件攔截到用戶請求時,將調用系統的業務邏輯組件,而業務邏輯組件則調用系統的DAO組件,而DAO組件則依賴于SessionFactory和DataSource等底層組件實現數據庫訪問。
從系統實現角度來看,IoC容器先創建SessionFactory和DataSource等底層組件,然后將這些底層組件注入給DAO組件,提供一個完整的DAO組件,并將此DAO組件注入給業務邏輯組件,從而提供一個完整的業務邏輯組件,而業務邏輯組件又被注入給控制器組件,控制器組件負責攔截用戶請求,并將處理結果呈現給用戶——這一系列的銜接都由Spring的IoC容器提供實現。
下面給出關于如何在容器中配置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">
??? <!-- 定義數據源Bean,使用C3P0數據源實現 -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
??? destroy-method="close">
??????? <!-- 指定連接數據庫的驅動 -->
??????? <property name="driverClass" value="com.mysql.jdbc.Driver"/>
??????? <!-- 指定連接數據庫的URL -->
??????? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
??????? <!-- 指定連接數據庫的用戶名 -->
??????? <property name="user" value="root"/>
??????? <!-- 指定連接數據庫的密碼 -->
??????? <property name="password" value="32147"/>
??????? <!-- 指定連接數據庫連接池的最大連接數 -->
??????? <property name="maxPoolSize" value="40"/>
??????? <!-- 指定連接數據庫連接池的最小連接數 -->
??????? <property name="minPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的初始化連接數 -->
??????? <property name="initialPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的連接最大空閑時間 -->
??????? <property name="maxIdleTime" value="20"/>
??? </bean>
??? <!-- 定義Hibernate的SessionFactory Bean -->
??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
??? LocalSessionFactoryBean">
??????? <!-- 依賴注入數據源,注入的正是上文中定義的dataSource -->
??????? <property name="dataSource" ref="dataSource"/>
??????? <!-- mappingResources屬性用來列出全部映射文件 -->
??????? <property name="mappingResources">
??????????? <list>
??????????????? <!-- 以下用來列出所有的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>
??????????????? <!-- 指定啟動應用時,是否根據Hibernate映射文件創建數據表 -->
??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>
??????? ???? </props>
??????? </property>
??? </bean>
??? <!-- 配置Person持久化類的DAO Bean -->
??? <bean id="personDao" class="lee.PersonDaoImpl">
??????? <!-- 采用依賴注入來傳入SessionFactory的引用 -->
??????? <property name="sessionFactory" ref="sessionFactory"/>
??? </bean>
??? <!-- 下面能以相同的方式配置更多的持久化Bean -->
??? ...
??? <bean id="myService" class="lee.MyServiceImp">
??????? <!-- 注入業務邏輯組件所必需的DAO組件 -->
??? ??? <property name="peronDdao" ref=" personDao "/>
??????? <!-- 此處可采用依賴注入更多的DAO組件 -->
??????? ...
??? </bean>
??? <!-- 配置控制器Bean,設置起作用域為Request -->
??? <bean name="/login" class="lee.LoginAction" scope="request">
??????? <!-- 依賴注入控制器所必需的業務邏輯組件 -->
??????? <property name="myService" ref=" myService "/>
??? </bean>
</beans>
在上面的配置文件中,同時配置了控制器Bean、業務邏輯組件Bean、DAO組件Bean以及一些基礎資源Bean。各組件的組織被解耦到配置文件中,而不是在代碼層次的低級耦合。
當客戶端的HTTP請求向/login.do發送請求時,將被容器中的lee.LoginAction攔截,LoginAction調用myService Bean,myService Bean則調用personDao等系列DAO組件,整個流程將系統中的各組件有機地組織在一起。
注意:在實際應用中,很少會將DAO組件、業務邏輯組件以及控制組件都配置在同一個文件中。而是在不同配置文件中,配置相同一組J2EE應用組件。
6.5.7 使用聲明式事務
在上面的配置文件中,部署了控制器組件、業務邏輯組件、DAO組件,幾乎可以形成一個完整的J2EE應用。但有一個小小的問題:事務控制。系統沒有任何事務邏輯,沒有事務邏輯的應用是不可想象的。
Spring提供了非常簡潔的聲明式事務控制,只需要在配置文件中增加事務控制片段,業務邏輯代碼無須任何改變。Spring的聲明式事務邏輯,甚至支持在不同事務策略之間切換。
配置Spring聲明式事務時,通常推薦使用BeanNameAutoProxyCreator自動創建事務代理。通過這種自動事務代理的配置策略,增加業務邏輯組件,只需要在BeanNameAutoProxyCreator Bean配置中增加一行即可,從而避免了增量式配置。
在上面的配置模板文件中增加如下配置片段,系統的myService業務邏輯組件將變成事務代理Bean,從而為業務邏輯方法增加事務邏輯。
??? <!-- 配置Hibernate的局部事務管理器 -->
??? <!-- 使用HibernateTransactionManager類,該類是PlatformTransactionManager
??? 接口,針對采用Hibernate持久化連接的特定實現 -->
??? <bean id="transactionManager"
????? ??? class="org.springframework.orm.hibernate3.
???????? HibernateTransactionManager">
??????? <!-- HibernateTransactionManager Bean需要依賴注入一個SessionFactory
??????? bean的引用 -->
??? ???? <property name="sessionFactory" ref="sessionFactory"/>
??? </bean>
??? <!-- 配置事務攔截器Bean -->
??? <bean id="transactionInterceptor"
??????? class="org.springframework.transaction.interceptor.
??????? TransactionInterceptor">
??????? <!-- 事務攔截器bean需要依賴注入一個事務管理器 -->
??????? <property name="transactionManager" ref="transactionManager"/>
??????? <property name="transactionAttributes">
??????????? <!-- 下面定義事務傳播屬性 -->
??????????? <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">
??? <!-- 指定對滿足哪些bean name的bean自動生成業務代理 -->
??? ??? <property name="beanNames">
??????????? <!-- 下面是所有需要自動創建事務代理的Bean -->
??????????? <list>
??????????????? <value>myService</value>
??????????????? <!-- 下面還可增加需要增加事務邏輯的業務邏輯Bean -->
??????????????? ...
??????????? </list>
??????????? <!-- 此處可增加其他需要自動創建事務代理的Bean -->
??? ??? </property>
??????? <!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->
??????? <property name="interceptorNames">
??????????? <list>
??????????????? <value>transactionInterceptor</value>
??????????????? <!-- 此處可增加其他新的Interceptor -->
??????????? </list>
??????? </property>
??? </bean>
一旦增加了如上的配置片段,系統中的業務邏輯方法就有了事務邏輯。這種聲明式事務配置方式可以在不同的事務策略之間自由切換。
提示:盡量使用聲明式事務配置方式,而不要在代碼中完成事務邏輯。
posted @
2009-07-19 10:24 jadmin 閱讀(378) |
評論 (0) |
編輯 收藏
6.5 Spring整合Hibernate
時至今日,可能極少有J2EE應用會直接以JDBC方式進行持久層訪問。畢竟,用面向對象的程序設計語言來訪問關系型數據庫,是一件讓人沮喪的事情。大部分時候,J2EE應用都會以ORM框架來進行持久層訪問,在所有的ORM框架中,Hibernate以其靈巧、輕便的封裝贏得了眾多開發者的青睞。
Spring具有良好的開放性,能與大部分ORM框架良好整合。下面將詳細介紹Spring與Hibernate的整合。
6.5.1 Spring提供的DAO支持
DAO模式是一種標準的J2EE設計模式,DAO模式的核心思想是,所有的數據庫訪 問,都通過DAO組件完成,DAO組件封裝了數據庫的增、刪、改等原子操作。而業務邏輯組件則依賴于DAO組件提供的數據庫原子操作,完成系統業務邏輯的實現。
對于J2EE應用的架構,有非常多的選擇,但不管細節如何變換,J2EE應用都大致可分為如下3層:
?? ● 表現層。
?? ● 業務邏輯層。
?? ● 數據持久層。
輕量級J2EE架構以Spring IoC容器為核心,承上啟下。其向上管理來自表現層的Action,向下管理業務邏輯層組件,同時負責管理業務邏輯層所需的DAO對象。各層之間負責傳值的是值對象,也就是JavaBean實例。
圖6.5精確地描繪了輕量級J2EE架構的大致情形。
DAO組件是整個J2EE應用的持久層訪問的重要組件,每個J2EE應用的底層實現都難以離開DAO組件的支持。Spring對實現DAO組件提供了許多工具類,系統的DAO組件可通過繼承這些工具類完成,從而可以更加簡便地實現DAO組件。
Spring的DAO支持,允許使用相同的方式、不同的數據訪問技術,如JDBC、Hibernate或JDO。Spring的DAO在不同的持久層訪問技術上提供抽象,應用的持久層訪問基于Spring的DAO抽象。因此,應用程序可以在不同的持久層技術之間切換。
Spring提供了一系列的抽象類,這些抽象將被作為應用中DAO實現類的父類。通過繼承這些抽象類,Spring簡化了DAO的開發步驟,能以一致的方式使用數據庫訪問技術。不管底層采用JDBC、JDO或Hibernate,應用中都可采用一致的編程模型。

圖6.5 輕量級J2EE應用架構
應用的DAO類繼承這些抽象類,會大大簡化應用的開發。最大的好處是,繼承這些抽象類的DAO能以一致的方式訪問數據庫,意味著應用程序可以在不同的持久層訪問技術中切換。
除此之外,Spring提供了一致的異常抽象,將原有的Checked異常轉換包裝成Runtime異常,因而,編碼時無須捕獲各種技術中特定的異常。Spring DAO體系中的異常,都繼承DataAccessException,而DataAccessException異常是Runtime的,無須顯式捕捉。通過DataAccessException的子類包裝原始異常信息,從而保證應用程序依然可以捕捉到原始異常信息。
Spring提供了多種數據庫訪問技術的DAO支持,包括Hibernate、JDO、TopLink、iBatis、OJB等。Spring可以使用相同的訪問模式、不同的數據庫訪問技術。就Hibernate的持久層訪問技術而言,Spring提供了如下3個工具類(或接口)來支持DAO組件的實現:
?? ● HibernateDaoSupport。
?? ● HibernateTemplate。
?? ● HibernateCallBack。
6.5.2 管理Hibernate的SessionFactory
前面介紹Hibernate時已經知道,在通過Hibernate進行持久層訪問時,Hibernate的SessionFactory是一個非常重要的對象,它是單個數據庫映射關系編譯后的內存鏡像。大部分情況下,一個J2EE應用對應一個數據庫,也即對應一個SessionFactory對象。
在純粹的Hibernate訪問中,應用程序需要手動創建SessionFactory實例,可想而知,這不是一個優秀的策略。在實際開發中,希望以一種聲明式的方式管理SessionFactory實例,直接以配置文件來管理SessionFactory實例,在示范Struts的PlugIn擴展點時,大致示范了這種方式(請參閱2.12.1節的內容)。
Spring的IoC容器則提供了更好的管理方式,它不僅能以聲明式的方式配置Session- Factory實例,也可充分利用IoC容器的作用,為SessionFactory注入數據源引用。
下面是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">
??? <!-- 定義數據源Bean,使用C3P0數據源實現 -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0. ComboPooledDataSource"
??? destroy-method="close">
??????? <!-- 指定連接數據庫的驅動 -->
??????? <property name="driverClass" value="com.mysql.jdbc.Driver"/>
??????? <!-- 指定連接數據庫的URL -->
??????? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
??????? <!-- 指定連接數據庫的用戶名 -->
??????? <property name="user" value="root"/>
??????? <!-- 指定連接數據庫的密碼 -->
??????? <property name="password" value="32147"/>
??????? <!-- 指定連接數據庫連接池的最大連接數 -->
??????? <property name="maxPoolSize" value="40"/>
??????? <!-- 指定連接數據庫連接池的最小連接數 -->
??????? <property name="minPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的初始化連接數 -->
??????? <property name="initialPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的連接最大空閑時間 -->
??????? <property name="maxIdleTime" value="20"/>
??? </bean>
??? <!-- 定義Hibernate的SessionFactory -->
??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
??? LocalSessionFactoryBean">
??????? <!-- 依賴注入數據源,正是上文定義的dataSource -->
??????? <property name="dataSource" ref="dataSource"/>
??????? <!-- mappingResources屬性用來列出全部映射文件 -->
??????? <property name="mappingResources">
??????????? <list>
??????????????? <!-- 以下用來列出所有的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>
??????????????? <!-- 配置啟動應用時,是否根據Hibernate映射自動創建數據表 -->
??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>
?????????? </props>
??????? </property>
??? </bean>
</beans>
一旦在Spring的IoC容器中配置了SessionFactory Bean,它將隨應用的啟動而加載,并可以充分利用IoC容器的功能,將SessionFactory Bean注入任何Bean,比如DAO組件。一旦DAO組件獲得了SessionFactory Bean的引用,就可以完成實際的數據庫訪問。
當然,Spring也支持訪問容器數據源。如果需要使用容器數據源,可將數據源Bean修改成如下配置:
<!-- 此處配置JNDI數據源 -->
<bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
??? <property name="jndiName">
??????? <!-- 指定數據源的JNDI -->
??????? <value>java:comp/env/jdbc/myds</value>
??? </property>
</bean>
可見,以聲明式的方式管理SessionFactory實例,可以讓應用在不同數據源之間切換。如果應用更換數據庫等持久層資源,只需對配置文件進行簡單修改即可。
提示:以聲明式的方式管理SessionFactory,非常類似于早期將數據庫服務的相關信息放在web.xml文件中進行配置。這種方式是為了提供更好的適應性,當持久層服務需要更改時,應用代碼無須任何改變。
6.5.3 使用HibernateTemplate
HibernateTemplate提供持久層訪問模板,使用HibernateTemplate無須實現特定接口,它只需要提供一個SessionFactory的引用就可執行持久化操作。SessionFactory對象既可通過構造參數傳入,也可通過設值方式傳入。HibernateTemplate提供如下3個構造函數:
?? ● HibernateTemplate()。
?? ● HibernateTemplate(org.hibernate.SessionFactory sessionFactory)。
?? ● HibernateTemplate(org.hibernate.SessionFactory sessionFactory, boolean allowCreate)。
第一個構造函數,構造一個默認的HibernateTemplate實例。因此,使用Hibernate- Template實例之前,還必須使用方法setSessionFactory(SessionFactory sessionFactory)來為HibernateTemplate傳入SessionFactory的引用。
第二個構造函數,在構造時已經傳入SessionFactory引用。
第三個構造函數,其boolean型參數表明,如果當前線程已經存在一個非事務性的Session,是否直接返回此非事務性的Session。
在Web應用中,通常啟動時自動加載ApplicationContext,SessionFactory和DAO對象都處在Spring上下文管理下,因此無須在代碼中顯式設置,可采用依賴注入完成Session- Factory和DAO的解耦,依賴關系通過配置文件來設置,如下所示:
<?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">
??? <!-- 定義數據源Bean,使用C3P0數據源實現 -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
??? destroy-method="close">
??????? <!-- 指定連接數據庫的驅動 -->
??????? <property name="driverClass" value="com.mysql.jdbc.Driver"/>
??????? <!-- 指定連接數據庫的URL -->
??????? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
??????? <!-- 指定連接數據庫的用戶名 -->
??????? <property name="user" value="root"/>
??????? <!-- 指定連接數據庫的密碼 -->
??????? <property name="password" value="32147"/>
??????? <!-- 指定連接數據庫連接池的最大連接數 -->
??????? <property name="maxPoolSize" value="40"/>
??????? <!-- 指定連接數據庫連接池的最小連接數 -->
??????? <property name="minPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的初始化連接數 -->
??????? <property name="initialPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的連接最大空閑時間 -->
??????? <property name="maxIdleTime" value="20"/>
??? </bean>
??? <!-- 定義Hibernate的SessionFactory Bean -->
??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
??? LocalSessionFactoryBean">
??????? <!-- 依賴注入數據源,注入的正是上文中定義的dataSource -->
??????? <property name="dataSource" ref="dataSource"/>
??????? <!-- mappingResources屬性用來列出全部映射文件 -->
??????? <property name="mappingResources">
??????????? <list>
??????????????? <!-- 以下用來列出所有的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>
??????????????? <!-- 指定啟動應用時,是否根據Hibernate映射文件創建數據表 -->
??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>
??????? ???? </props>
??????? </property>
??? </bean>
??? <!-- 配置Person持久化類的DAO bean -->
??? <bean id="personDao" class="lee.PersonDaoImpl">
??? ??? <!-- 采用依賴注入來傳入SessionFactory的引用 -->
??????? <property name="sessionFactory" ref="sessionFactory"/>
??? </bean>
</beans>
在PersonDao組件中,所有的持久化操作都通過HibernateTemplate實例完成,而HibernateTemplate操作數據庫非常簡潔,大部分CRUD操作都可通過一行代碼解決問題。下面介紹如何通過HibernateTemplate進行持久層訪問。
HibernateTemplate提供了非常多的常用方法來完成基本的操作,比如通常的增加、刪除、修改、查詢等操作,Spring 2.0更增加了對命名SQL查詢的支持,也增加了對分頁的支持。大部分情況下,使用Hibernate的常規用法,就可完成大多數DAO對象的CRUD操作。下面是HibernateTemplate的常用方法簡介:
?? ● void delete(Object entity),刪除指定持久化實例。
?? ● deleteAll(Collection entities),刪除集合內全部持久化類實例。
?? ● find(String queryString),根據HQL查詢字符串來返回實例集合。
?? ● findByNamedQuery(String queryName),根據命名查詢返回實例集合。
?? ● get(Class entityClass, Serializable id),根據主鍵加載特定持久化類的實例。
?? ● save(Object entity),保存新的實例。
?? ● saveOrUpdate(Object entity),根據實例狀態,選擇保存或者更新。
?? ● update(Object entity),更新實例的狀態,要求entity是持久狀態。
?? ● setMaxResults(int maxResults),設置分頁的大小。
下面是一個完整DAO類的源代碼:
public class PersonDaoImpl implements PersonDao
{
??? //執行持久化操作的HibernateTemplate實例
??? private HibernateTemplate ht = null;
??? private SessionFactory sessionFactory;
??? //該DAO組件持久化操作所需的SessionFactory對象
??? public void setSessionFactory(SessionFactory sessionFactory)
??? {
??????? this.sessionFactory = sessionFactory;
??? }
??? //用于根據SessionFactory實例返回HibernateTemplate實例的方法
??? private HibernateTemplate getHibernateTemplate()
??? {
??????? if (ht == null)
??????? {
??????????? ht = new HibernateTemplate(sessionFactory);
??????? }
??????? return ht;
??? }
??? /**
???? * 加載人實例
???? * @param id 需要加載的Person實例的主鍵值
???? * @return 返回加載的Person實例
???? */
??? public Person get(int id)
??? {
??????? return (Person)getHibernateTemplate().get(Person.class, new
??????? Integer(id));
??? }
??? /**
???? * 保存人實例
???? * @param person 需要保存的Person實例
???? */???
??? public void save(Person person)
??? {
??????? getHibernateTemplate().save(person);
??? }
??? /**
???? * 修改Person實例
???? * @param person 需要修改的Person實例
???? */
??? public void update(Person person)
??? {
??????? getHibernateTemplate().update(person);
??? }
??? /**
???? * 刪除Person實例
???? * @param id 需要刪除的Person的id
???? */
??? public void delete(int id)
??? {
??????? getHibernateTemplate().delete(getHibernateTemplate().get(Person.
??????? class,new Integer(id)));
??? }
??? /**
???? * 刪除Person實例
???? * @param person 需要刪除的Person實例
???? */
?? public void delete(Person person)
??? {
??????? getHibernateTemplate().delete(person);
??? }
??? /**
???? * 根據用戶名查找Person
???? * @param name 用戶名
???? * @return 用戶名對應的全部用戶
???? */
??? public List findByName(String name)
??? {
??????? return getHibernateTemplate().find("from Person p where p.name
??????? like ?" , name);
??? }
??? /**
??? * 返回全部的Person實例
??? * @return 全部的Person實例
??? */
??? public List findAllPerson()
??? {
??????? return getHibernateTemplate().find("from Person ");
??? }
}
通過上面實現DAO組件的代碼可以看出,通過HibernateTemplate進行持久層訪問的代碼如此清晰,大部分CRUD操作一行代碼即可完成,完全無須Hibernate訪問那些繁瑣的步驟。而且,一旦DAO組件獲得了SessionFactory的引用,即可很輕易地創建HibernateTemplate實例。
提示:HibernateTemplate是Spring眾多模板工具類之一,Spring正是通過這種簡便地封裝,完成了開發中大量需要重復執行的工作。
posted @
2009-07-19 10:24 jadmin 閱讀(675) |
評論 (0) |
編輯 收藏
Struts的plug-in配置部分明確指出,Spring的配置文件有兩個:applicationContext.xml和action-servlet.xml。其實,完全可以使用一個配置文件。通常,習慣將Action Bean配置在控制器的context內。action-servlet.xml用于配置表現層上下文,其詳細配置信息如下:
<?xml version="1.0" encoding="gb2312"?>
<!-- 指定Spring配置文件的根元素,以及對應的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">
??? <!-- 每個request請求產生一個新實例,將所有該請求的作用域配置成request -->
??? <bean name="/login" class="lee.LoginAction" scope="request">
??????? <property name="vb" ref="vb"/>
??? </bean>
</beans>
因為每次請求都應該啟動新的Action處理用戶請求,因此,應將Action的作用域配置成Request。
注意:ActionServlet轉發請求時,是根據Bean的name屬性,而不是id屬性。因此,此處確定的name屬性與Struts的action屬性相同。
applicationContext.xml只有一個bean配置,即vb bean。其詳細配置如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定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">
??? <!-- 配置ValidBean實例 -->
??? <bean id="vb" class="lee.ValidBeanImpl"/>
</beans>
ValidBeanImpl是一個業務邏輯bean,本示例程序中僅作簡單的判斷,ValidBeanImpl的源代碼如下:
//面向接口編程,實現ValidBean接口
public class ValidBeanImpl implements ValidBean
{
??? //根據輸入的用戶名和密碼判斷是否有效
??? public boolean valid(String username,String pass)
??? {
??????? //有效,返回true
??????? if (username.equals("scott") && pass.equals("tiger"))
??????? {
??????????? return true;
??????? }
??????? return false;
??? }
}
注意:上面的業務邏輯組件非常簡單,它只是一個示意。如果是真實的應用,業務邏輯組件應該通過DAO組件來實現業務邏輯方法。
應用的業務邏輯控制器,Action則負責調用業務邏輯組件的方法,并根據業務邏輯組件方法的返回值,確定如何響應用戶請求。下面是該示例應用控制器的代碼:
//業務控制器繼承Action
public class LoginAction extends Action
{
??? //action控制器將調用的業務邏輯組件
??? private ValidBean vb;
??? //依賴注入業務邏輯組件的setter方法
??? public void setVb(ValidBean vb)
??? {
??????? this.vb = vb;
??? }
??? //必須重寫該核心方法,該方法actionForm將表單的請求參數封裝成值對象
??? public ActionForward execute(ActionMapping mapping, ActionForm form,
?????? HttpServletRequest request, HttpServletResponse response)throws
?????? Exception
??? {
??????? //form由ActionServlet轉發請求時創建,封裝了所有的請求參數
??????? LoginForm loginForm = (LoginForm)form;
??????? //獲取username請求參數
??????? String username = loginForm.getUsername();
??????? //獲取pass請求參數
??????? String pass = loginForm.getPass();
??????? //下面為服務器端的數據校驗
??????? String errMsg = "";
??????? //判斷用戶名不能為空
??????? if (username == null || username.equals(""))
??????? {
??????????? errMsg += "您的用戶名丟失或沒有輸入,請重新輸入";
??????? }
??????? //判斷密碼不能為空
??????? else if(pass == null || pass.equals(""))
??????? {
??????????? errMsg += "您的密碼丟失或沒有輸入,請重新輸入";
??????? }
??????? //如果用戶名和密碼不為空,才調用業務邏輯組件
??????? else
??????? {
??????????? //vb是業務邏輯組件,由容器注入
??????????? if (vb.valid(username,pass))
??????????? {
??????????????? return mapping.findForward("welcome");
??????????? }
??????????? else
??????????? {
??????????????? errMsg = "您的用戶名和密碼不匹配";
??????????? }
??????? }
??????? //判斷是否生成了錯誤信息
??????? if (errMsg != null && !errMsg.equals(""))
??????? {
??????????? //如果有錯誤信息,將錯誤信息保存在request里,并跳轉到input對應的
??????????? forward對象
??????????? request.setAttribute("err" , errMsg);
??????????? return mapping.findForward("input");
??????? }
??????? else
??????? {
??????????? //如果沒有錯誤信息,跳轉到welcome對應的forward對象
??????????? return mapping.findForward("welcome");
??????? }
??? }
}
在本應用中,使用了Struts的客戶端數據校驗,讓Action繼承ValidatorActionForm即可。ActionForm的代碼非常簡單,此處不再贅述。
為了完成數據校驗,還應該編寫數據校驗規則文件。在struts-config.xml文件的尾部,另有一個plug-in用來加載校驗文件,其中validator-rules.xml文件位于struts壓縮包的lib下,直接復制過來即可使用,而validator.xml必須自己編寫,validator.xml文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 驗證規則文件的文件頭,包括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">
<!-- 驗證文件的根元素 -->
<form-validation>
??? <!-- 所有需要驗證的form都放在formset里 -->
<formset>
??? <!-- 需要驗證的form名,該名與struts里配置的名相同 -->
??? <form name="loginForm">
??????? <!-- 指定該form的username域必須滿足的規則:必填、模式匹配 -->
??????? <field property="username" depends="required,mask">
??????? ??? <arg key="loginForm.username" position="0"/>
??????????? <var>
??????????????? <!-- 確定匹配模式的正則表達式 -->
??????????? <var-name>mask</var-name>
??????????? <var-value>^[a-zA-Z]+$</var-value>
??????????? </var>
??????? </field>
??????? <!-- 指定該form的pass域必須滿足的規則:必填 -->
??????? <field property="pass" depends="required">
??????????? <msg name="required" key="pass.required"/>
??????? ??? <arg key="loginForm.pass" position="0"/>
??????? </field>
??? </form>
</formset>
</form-validation>
上面示例程序的結構非常清晰:表現層組件(Action)配置在action-servlet.xml文件中,而業務邏輯層組件(vb)配置在applicationContext.xml文件中,如果應用中有DAO組件,將DAO組件配置在dao-context.xml文件中。將3個文件放在plug-in元素里一起加載。
DelegatingRequestProcessor會將請求轉發到Action,該Action已經處于IoC容器管理之下,因此,可以方便地訪問容器中的其他Bean。
通過配置文件可以看出,Action根本無須type屬性,即struts-config.xml中Action根本沒有實例化過,DelegatingRequestProcessor將請求轉發給Spring容器中的同名Bean。這種轉發的時機非常早,避免了創建struts-config.xml配置文件中的Action,因而性能非常好。
圖6.3是采用這種整合策略的執行效果。
6.4.4 使用DelegatingActionProxy
使用DelegatingRequestProcessor簡單方便,但有一個缺點,RequestProcessor是Struts的一個擴展點,也許應用程序本身就需要擴展RequestProcessor,而DelegatingRequest- Processor已經使用了這個擴展點。
為了重新利用Struts的RequestProcessor這個擴展點,有兩個做法:
?? ● 應用程序的RequestProcessor不再繼承Struts的RequestProcessor,改為繼承DelegatingRequestProcessor。
?? ● 使用DelegatingActionProxy。
前者常常有一些未知的風險,而后者是Spring推薦的整合策略。使用Delegating- ActionProxy與DelegatingRequestProcessor的目的都只有一個,將請求轉發給Spring管理的Bean。
DelegatingRequestProcessor直接替換了原有的RequestProcessor,在請求轉發給action之前,轉發給Spring管理的Bean;而DelegatingActionProxy則被配置成Struts的Action,即所有的請求先被ActionServlet截獲,請求被轉發到對應的Action,而action的實現類全都是DelegatingActionProxy,DelegatingActionProxy再將請求轉發給Spring容器的Action。
可以看出,使用DelegatingActionProxy比使用DelegatingRequestProcessor要晚一步轉發到Spring的context。但通過這種方式可以避免占用擴展點。
與使用DelegatingRequestProcessor相比,使用DelegatingActionProxy僅需要去掉controller配置元素,并將所有的action實現類改為DelegatingActionProxy即可。詳細的配置文件如下:
<!-- 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>
??????? <!-- 定義了一個formbean,確定formbean名和實現類 -->
??????? <form-bean name="loginForm" type="lee.LoginForm"/>
??? </form-beans>
??? <!-- 定義action部分,所有的action都放在action-mapping元素里定義 -->
??? <action-mappings>
??????? <!-- 這里只定義了一個action。必須配置action的type元素為
??? ??? DelegatingActionProxy -->
??????? <action path="/login" type="org.springframework.web.struts.
??????? DelegatingActionProxy"
??????????? name="loginForm" scope="request" validate="true" input=
??????????? "/login.jsp" >
??????????? <!-- 定義action內的兩個局部forward元素 -->
??????????? <forward name="input" path="/login.jsp"/>
??????????? <forward name="welcome" path="/welcome.html"/>
??????? </action>
??? </action-mappings>
??? <!-- 加載國際化的資源包 -->
??? <message-resources parameter="mess"/>
??? <!-- 裝載驗證的資源文件 -->
??? <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配置文件,隨應用啟動創建ApplicationContext實例 -->
??? <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轉發過來的請求,然后轉發給Application- Context管理的Bean,這是典型的鏈式處理。
通過配置文件可以看出,struts-config.xml文件中配置了大量DelegatingActionProxy實例,Spring容器中也配置了同名的Action。即Struts的業務控制器分成了兩個部分:第一個部分是Spring的DelegatingActionProxy,這個部分沒有實際意義,僅僅完成轉發;第二個部分是用戶的Action實現類,該實現類負責真實的處理。
這種策略的性能比前一種策略的效果要差一些,因為需要多創建一個Delegating- ActionProxy實例。而且,J2EE應用中Action非常多,這將導致大量創建DelegatingActionProxy實例,使用一次之后,等待垃圾回收機制回收——這對性能的影響不可避免。
圖6.4是DelegatingActionProxy的執行效果。
注意:使用DelegatingActionProxy的整合策略,可避免占用Struts的RequestProcessor擴展點,但降低了整合性能。
6.4.5 使用ActionSupport代替Action
前面已經介紹了,Spring與Struts的整合還有一種策略,讓Struts的Action顯式獲取Spring容器中的Bean。在這種策略下,Struts的Action不接受IoC容器管理,Action的代碼與Spring API部分耦合,造成代碼污染。這種策略也有其好處:代碼的可讀性非常強,Action的代碼中顯式調用業務邏輯組件,而無須等待容器注入。
Action中訪問ApplicationContext有兩種方法:
?? ● 利用WebApplicationContextUtils工具類。
?? ● 利用ActionSupport支持類。
通過WebApplicationContextUtils,可以顯式獲得Spring容器的引用(請參閱6.4.1節的內容),而ActionSupport類則提供了一個更簡單的方法getWebApplicationContext(),該方法可直接獲取Spring容器的引用。
所謂ActionSupport類,是指Spring提供了系列擴展。Spring擴展了Struts的Action,在Struts的Action后加上Support后綴,Spring擴展的Action有如下幾個:
?? ● ActionSupport。
?? ● DispatchActionSupport。
?? ● LookupDispatchActionSupport。
?? ● MappingDispatchActionSupport。
下面的示例將展示這種整合策略,在這種整合策略下,Struts的Action改為繼承Spring擴展后的Action,下面是應用的Action代碼:
//新的業務控制器,繼承Spring的ActionSupport類
public class LoginAction extends ActionSupport
{
??? //依然將ValidBean作為成員變量
??? private ValidBean vb;
??? //構造器,注意:不可在構造器中調用getWebApplicationContext()方法
??? public LoginAction()
??? {
??? }
??? //完成ValidBean的初始化
??? public ValidBean getVb()
??? {
??????? return(ValidBean)getWebApplicationContext().getBean("vb");
??? }
??? //必須重寫該核心方法,該方法actionForm將表單的請求參數封裝成值對象
??? public ActionForward execute(ActionMapping mapping, ActionForm form,
?????? HttpServletRequest request, HttpServletResponse response)throws
?????? Exception
??? {
??????? //form由ActionServlet轉發請求時創建,封裝了所有的請求參數
??????? LoginForm loginForm = (LoginForm)form;
??????? //獲取username請求參數
??????? String username = loginForm.getUsername();
??????? //獲取pass請求參數
??????? String pass = loginForm.getPass();
??????? //下面為服務器端的數據校驗
??????? String errMsg = "";
??????? //判斷用戶名不能為空
??????? if (username == null || username.equals(""))
??????? {
??????????? errMsg += "您的用戶名丟失或沒有輸入,請重新輸入";
??????? }
??????? //判斷密碼不能為空
??????? else if(pass == null || pass.equals(""))
??????? {
??????????? errMsg += "您的密碼丟失或沒有輸入,請重新輸入";
??????? }
??????? //如果用戶名和密碼不為空,才調用業務邏輯組件
??????? else
??????? {
??????????? //vb是業務邏輯組件,通過上面的初始化方法獲得
??????????? if (getVb().valid(username,pass))
??????????? {
??????????????? return mapping.findForward("welcome");
??????????? }
??????????? else
??????????? {
??????????????? errMsg = "您的用戶名和密碼不匹配";
??????????? }
??????? }
??????? //判斷是否生成了錯誤信息
??????? if (errMsg != null && !errMsg.equals(""))
??????? {
??????????? //如果有錯誤信息,將錯誤信息保存在request里,并跳轉到input對應的
??????????? //forward對象
??????????? request.setAttribute("err" , errMsg);
??????????? return mapping.findForward("input");
??????? }
??????? else
??????? {
??????????? //如果沒有錯誤信息,跳轉到welcome對應的forward對象
??????????? return mapping.findForward("welcome");
??????? }
??? }
}
在上面的Action代碼中,Action顯式獲取容器中的業務邏輯組件,而不是依靠Spring容器的依賴注入。在這種整合策略下,表現層的控制器組件不再接受IoC容器管理。因此,沒有控制器上下文,應將原有的action-servlet.xml文件刪除,并修改plug-in元素,不要加載該文件。還要修改Action配置,將Action配置的type元素修改成實際的處理類。這????? 種整合策略也有一個好處:代碼的可讀性更強,對傳統Struts應用開發的改變很小,容易使用。
將該Action部署在struts-config.xml中,Struts將負責創建該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>
??????? <!-- 定義了一個formbean,確定formbean名和實現類 -->
??????? <form-bean name="loginForm" type="lee.LoginForm"/>
??? </form-beans>
??? <!-- 定義action部分,所有的action都放在action-mapping元素里定義 -->
??? <action-mappings>
??????? <!-- 這里只定義了一個action。action的類型為ActionSupport的子類 -->
??????? <action path="/login" type="type="lee.LoginAction"
??????????? name="loginForm" scope="request" validate="true" input=
??????????? "/login.jsp" >
??????????? <!-- 定義action內的兩個局部forward元素 -->
??????????? <forward name="input" path="/login.jsp"/>
??????????? <forward name="welcome" path="/welcome.html"/>
??????? </action>
??? </action-mappings>
??? <!-- 加載國際化的資源包 -->
??? <message-resources parameter="mess"/>
??? <!-- 裝載驗證的資源文件 -->
??? <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>
此時,Spring無須使用配置Action的配置文件,這種配置方式非常簡單。只需要業務邏輯組件的配置文件,業務邏輯組件的配置文件與前面的示例沒有任何改變。
該配置文件中的業務邏輯組件由Spring容器負責實現,而ActionSupport能夠先定位Spring容器,然后獲得容器的業務邏輯組件。
這種整合策略的執行效果與前面兩種整合策略的執行效果完全相同。從代碼中分析可見,在這種整合策略下,業務控制器再次退回到Struts起初的設計。僅由strutsconfig.xml中Action充當,從而避免了像DelegatingActionProxy整合策略的性能低下,因為可以只需要創建實際的Action實例。
注意:在這種整合策略下,Struts開發者的改變最小,最接近傳統Struts應用開發者的習慣。但這種整合策略會造成代碼污染,因為Action類必須繼承Spring的ActionSupport類。
posted @
2009-07-19 10:23 jadmin 閱讀(76) |
評論 (0) |
編輯 收藏
6.4 Spring整合Struts
雖然Spring也提供了自己的MVC組件,但一來Spring的MVC組件過于繁瑣,二???? 來Struts的擁護者實在太多。因此,很多項目都會選擇使用Spring整合Struts框架。而且Spring確實可以無縫整合Struts框架,二者結合成一個更實際的J2EE開發平臺。
6.4.1 利用Struts的PlugIn來啟動Spring容器
使用Spring的Web應用時,不用手動創建Spring容器,而是通過配置文件聲明式地創建Spring容器。因此,在Web應用中創建Spring容器有如下兩個方式:
?? ● 直接在web.xml文件中配置創建Spring容器。
?? ● 利用第三方MVC框架的擴展點,創建Spring容器。
其實第一種創建Spring容器的方式更加常見。為了讓Spring容器隨Web應用的啟動而自動啟動,有如下兩個方法:
?? ● 利用ServletContextListener實現。
?? ● 采用load-on-startup Servlet實現。
Spring提供ServletContextListener的一個實現類ContextLoaderListener,該類可以作為Listener使用,會在創建時自動查找WEB-INF/下的applicationContext.xml文件,因此,如果只有一個配置文件,并且文件名為applicationContext.xml,只需在web.xml文件中增加如下配置片段即可:
<listener>
?? <listener-class>org.springframework.web.context.
??? ContextLoaderListener</listener-class>
</listener>
如果有多個配置文件需要載入,則考慮使用<context-param>元素來確定配置文件的文件名。ContextLoaderListener加載時,會查找名為contextConfigLocation的參數。因此,配置context-param時,參數名字應該是contextConfigLocation。
帶多個配置文件的web.xml文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Web配置文件的根元素,以及相應的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">
??? <!-- 確定多個配置文件 -->
??? <context-param>
??????? <!-- 參數名為contextConfigLocation -->
??????? <param-name>contextConfigLocation</param-name>
??????? <!-- 多個配置文件之間以“,”隔開 -->
??????? <param-value>/WEB-INF/daoContext.xml,/WEB-INF/
??????? applicationContext.xml</param-value>
??? </context-param>
??? <!-- 采用listener創建ApplicationContext實例 -->
??? <listener>
??????? <listener-class>org.springframework.web.context.
??????? ContextLoaderListener</listener-class>
??? </listener>
</web-app>
如果沒有通過contextConfigLocation指定配置文件,Spring會自動查找application- Context.xml配置文件;如果有contextConfigLocation,則利用該參數確定的配置文件。如果無法找到合適的配置文件,Spring將無法正常初始化。
Spring根據bean定義創建WebApplicationContext對象,并將其保存在web應用的ServletContext中。大部分情況下,應用中的Bean無須感受到ApplicationContext的存在,只要利用ApplicationContext的IoC即可。
如果需要在應用中獲取ApplicationContext實例,可以通過如下代碼獲取:
//獲取當前Web應用的Spring容器
WebApplicationContext ctx =
??? WebApplicationContextUtils.getWebApplicationContext(servletContext);
除此之外,Spring提供了一個特殊的Servlet類ContextLoaderServlet。該Servlet在啟動時,會自動查找WEB-INF/下的applicationContext.xml文件。
當然,為了讓ContextLoaderServlet隨應用的啟動而啟動,應將此Servlet配置成load-on-startup的Servlet,load-on-startup的值小一點比較合適,這樣可以保證Application- Context更快的初始化。
如果只有一個配置文件,并且文件名為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用于提供“后臺”服務,主要用于創建Spring容器,無須響應客戶請求,因此無須配置servlet-mapping。
如果有多個配置文件,一樣使用<context-param>元素來確定多個配置文件。
事實上,不管是ContextLoaderServlet,還是ContextLoaderListener,都依賴于ContextLoader創建ApplicationContext實例。
在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的參數,如果有該參數,則加載該參數指定的配置文件。
ContextLoaderServlet與ContextLoaderListener底層都依賴于ContextLoader。因此,二者的效果幾乎沒有區別。之間的區別不是它們本身引起的,而是由于Servlet規范,Listener比Servlet優先加載。因此,采用ContextLoaderListener創建ApplicationContext的時機更早。
當然,也可以通過ServletContext的getAttribute方法獲取ApplicationContext。但使用WebApplicationContextUtils類更便捷,因為無須記住ApplicationContext的屬性名。即使ServletContext的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRI- BUTE屬性沒有對應對象,WebApplicationContextUtils的getWebApplicationContext()方法將會返回空,而不會引起異常。
到底需要使用Listener,還是使用load-on-startup Servlet來創建Spring容器呢?通常推薦使用Listener來創建Spring容器。但Listerner是Servlet 2.3以上才支持的標準,因此,必須Web容器支持Listener才可使用Listerner。
注意:使用Listener創建Spring容器之前,應先評估Web容器是否支持Listener標準。
還有一種情況,利用第三方MVC框架的擴展點來創建Spring容器,比如Struts。在第2章介紹Strust框架時,知道Struts有一個擴展點PlugIn。
實際上,Spring正是利用了PlugIn這個擴展點,從而提供與Struts的整合。Spring提供了PlugIn接口的實現類org.springframework.web.struts.ContextLoaderPlugIn。這個實現類可作為Struts的PlugIn配置,Struts框架啟動時,將自動創建Spring容器。
為了利用Struts的PlugIn創建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屬性值時,即可以指定一個Spring配置文件的位置,可以指定多個Spring配置文件的位置。
6.4.2 MVC框架與Spring整合的思考
對于一個基于B/S架構的J2EE應用而言,用戶請求總是向MVC框架的控制器請求,而當控制器攔截到用戶請求后,必須調用業務邏輯組件來處理用戶請求。此時有一個問題,控制器應該如何獲得業務邏輯組件?
最容易想到的策略是,直接通過new關鍵字創建業務邏輯組件,然后調用業務邏輯組件的方法,根據業務邏輯方法的返回值確定結果。
實際的應用中,很少見到采用上面的訪問策略,因為這是一種非常差的策略。不這樣做至少有如下3個原因:
?? ● 控制器直接創建業務邏輯組件,導致控制器和業務邏輯組件的耦合降低到代碼層次,不利于高層次解耦。
?? ● 控制器不應該負責業務邏輯組件的創建,控制器只是業務邏輯組件的使用者。無須關心業務邏輯組件的實現。
?? ● 每次創建新的業務邏輯組件將導致性能下降。
答案是采用工廠模式或服務定位器。采用服務定位器的模式,是遠程訪問的場景。在這種場景下,業務邏輯組件已經在某個容器中運行,并對外提供某種服務。控制器無須理會該業務邏輯組件的創建,直接調用即可,但在調用之前,必須先找到該服務——這就是服務定位器的概念。經典J2EE應用就是這種結構的應用。
對于輕量級的J2EE應用,工廠模式則是更實際的策略。因為輕量級的J2EE應用里,業務邏輯組件不是EJB,通常就是一個POJO,業務邏輯組件的生成通常由工廠負責,而且工廠可以保證該組件的實例只需一個就夠了,可以避免重復實例化造成的系統開銷。
如圖6.2就是采用工廠模式的順序圖。

圖6.2 工廠模式順序圖
采用工廠模式,將控制器與業務邏輯組件的實現分離,從而提供更好的解耦。
在采用工廠模式的訪問策略中,所有的業務邏輯組件的創建由工廠負責,業務邏輯組件的運行也由工廠負責。而控制器只需定位工廠實例即可。
如果系統采用Spring框架,則Spring成為最大的工廠。Spring負責業務邏輯組件的創建和生成,并可管理業務邏輯組件的生命周期。可以如此理解,Spring是一個性能非常優秀的工廠,可以生產出所有的實例,從業務邏輯組件,到持久層組件,甚至控制器。
現在的問題是,控制器如何訪問到Spring容器中的業務邏輯組件?為了讓Action訪 問Spring的業務邏輯組件,有兩種策略:
?? ● Spring管理控制器,并利用依賴注入為控制器注入業務邏輯組件。
?? ● 控制器顯式定位Spring工廠,也就是Spring的容器ApplicationContext實例,并從工廠中獲取業務邏輯組件實例的引用。
第一種策略,充分利用Spring的IoC特性,是最優秀的解耦策略。但不可避免帶來一些不足之處,歸納起來主要有如下不足之處:
?? ● Spring管理Action,必須將所有的Action配置在Spring容器中,而struts-config.xml文件中的配置也不會減少,導致配置文件大量增加。
?? ● Action的業務邏輯組件接收容器注入,將導致代碼的可讀性降低。
總體而言,這種整合策略是利大于弊。
第二種策略,與前面介紹的工廠模式并沒有太大的不同。區別是Spring容器充當了業務邏輯組件的工廠。控制器負責定位Spring容器,通常Spring容器訪問容器中的業務邏輯組件。這種策略是一種折衷,降低了解耦,但提高了程序的可讀性。
Spring完全支持這兩種策略,既可以讓Spring容器管理控制器,也可以讓控制器顯式定位Spring容器中的業務邏輯組件。
6.4.3 使用DelegatingRequestProcessor
這里介紹的是第一種整合策略:讓Spring管理Struts的Action。那么同樣有一個問題,讓Spring管理Struts的Action時,客戶端的HTTP 請求如何轉向Spring容器中的Action?
當使用Struts作為MVC框架時,客戶端的HTTP請求都是直接向ActionServlet請求的,因此關鍵就是讓ActionServlet將請求轉發給Spring容器中的Action。這很明顯可以利用Spring的另一個擴展點:通過擴展RequestProcessor完成,使用擴展的RequestProcessor替換Struts的RequestProcessor。
Spring完成了這種擴展,Spring提供的DelegatingRequestProcessor繼承Request- Processor。為了讓Struts使用DelegatingRequestProcessor,還需要在struts-config.xml文件中增加如下一行:
//使用spring的RequestProcessor替換struts原有的RequestProcessor
<controller processorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/>
完成這個設置后,Struts會將截獲到的用戶請求轉發到Spring context下的bean,根據bean的name屬性來匹配。而Struts中的action配置則無須配置class屬性,即使配置了class屬性也沒有任何用處,即下面兩行配置是完全一樣的:
//配置struts action時,指定了實現類
<action path="/user" type="lee.UserAction"/>
//配置struts action時,沒有指定實現類
<action path="/user"/>
下面的示例程序在上一個示例程序的基礎上稍作修改,增加了客戶端驗證和程序國際化部分。也調用了Spring的業務bean來驗證登錄。先看修改后的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>
??????? <!-- 定義了一個formbean,確定formbean名和實現類 -->
??????? <form-bean name="loginForm" type="lee.LoginForm"/>
??? </form-beans>
??? <!-- 定義action部分,所有的action都放在action-mapping元素里定義 -->
??? <action-mappings>
??????? <!-- 這里只定義了一個action。而且沒有指定該action的type元素 -->
??????? <action path="/login" name="loginForm"
??????????? scope="request" validate="true" input="/login.jsp" >
??????????? <!-- 定義action內的兩個局部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"/>
??? <!-- 加載國際化的資源包 -->
??? <message-resources parameter="mess"/>
??? <!-- 裝載驗證的資源文件 -->
??? <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配置文件,隨應用的啟動創建ApplicationContext實例 -->
??? <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文件,增加加載國際化資源文件。配置Struts的action不需要class屬性,完成了ApplicationContext的創建。
然后考慮web.xml文件的配置,在web.xml文件中必須配置Struts框架的加載。除此之外,因為使用了Spring管理Struts的Action,而Action是隨HTTP請求啟動的,因此,應將Action的作用域配置成Request,為了使用Request作用域,必須在web.xml文件中增加適當的配置。
下面是web.xml文件的代碼:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Web配置文件的根元素,以及對應的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">
??? <!-- 定義一個Filter,該Filter是使用Request作用域的基礎 -->
??? <filter>
??????? <filter-name>requestContextFilter</filter-name>
??????? <filter-class>org.springframework.web.filter.
??????? RequestContextFilter </filter-class>
??? </filter>
??? <!-- 定義filter-mapping,讓上面的Filter過濾所有的用戶請求 -->
??? <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請求 -->
??? <servlet-mapping>
??????? <servlet-name>action</servlet-name>
??????? <url-pattern>*.do</url-pattern>
??? </servlet-mapping>
??? <!-- 關于Struts標簽庫的配置 -->
??? <jsp-config>
??????? <!-- 配置bean標簽 -->
??????? <taglib>
??????????? <taglib-uri>/tags/struts-bean</taglib-uri>
??????????? <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
??????? </taglib>
??????? <!-- 配置html標簽 -->
??????? <taglib>
??????????? <taglib-uri>/tags/struts-html</taglib-uri>
??????????? <taglib-location>/WEB-INF/struts-html.tld</taglib-location>
??????? </taglib>
??????? <!-- 配置logic標簽 -->
??????? <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 閱讀(68) |
評論 (0) |
編輯 收藏
6.3.2 Spring事務策略的優勢
雖然在上面的配置片段中,僅僅配置了JDBC局部事務管理器、Hibernate局部事務管理器、JDBC全局事務管理器等。但Spring支持大部分持久化策略的事務管理器。
不論采用何種持久化策略,Spring都提供了一致的事務抽象,因此,應用開發者能在任何環境下,使用一致的編程模型。無須更改代碼,應用就可在不同的事務管理策略中切換。Spring同時支持聲明式事務管理和編程式事務管理。
使用編程式事務管理,開發者使用的是Spring事務抽象,而無須使用任何具體的底層事務API。Spring的事務管理將代碼從底層具體的事務API中抽象出來,該抽象可以使用在任何底層事務基礎之上。
使用聲明式策略,開發者通常書寫很少的事務管理代碼,因此,不依賴Spring或任何其他事務API。Spring的聲明式事務無須任何額外的容器支持,Spring容器本身管理聲明式事務。使用聲明事務策略,無須在業務代碼中書寫任何事務代碼,可以讓開發者更好地專注于業務邏輯的實現。Spring管理的事務支持多個事務資源的跨越,但無法支持跨越遠程調用的事務上下文傳播。
6.3.3 使用TransactionProxyFactoryBean創建事務代理
Spring同時支持編程式事務策略和聲明式事務策略,大部分時候,都推薦采用聲明式事務策略,使用聲明事務策略的優勢十分明顯:
?? ● 聲明式事務能大大降低開發者的代碼書寫量。而且聲明式事務幾乎不需要影響應用的代碼。因此,無論底層事務策略如何變化,應用程序無須任何改變。
?? ● 應用程序代碼無須任何事務處理代碼,可以更專注于業務邏輯的實現。
?? ● Spring則可對任何POJO的方法提供事務管理,而且Spring的聲明式事務管理無須容器的支持,可在任何環境下使用。
?? ● EJB的CMT無法提供聲明式回滾規則。而通過配置文件,Spring可指定事務在遇到特定異常時自動回滾。Spring不僅可在代碼中使用setRollbackOnly回滾事務,也可在配置文件中配置回滾規則。
?? ● 由于Spring采用AOP的方式管理事務,因此,可以在事務回滾動作中插入用戶自己的動作,而不僅僅是執行系統默認的回滾。
提示:本節不打算全面介紹Spring的各種事務策略,因此本節不會介紹編程式事務。如果讀者需要更全面了解Spring事務的相關方面,請參閱筆者所著的《Spring2.0寶典》???? 一書。
對于采用聲明式事務策略,可以使用TransactionProxyFactoryBean來配置事務代理Bean。正如它的類名所暗示的,它是一個工廠Bean,工廠Bean用于生成一系列的Bean實例,這一系列的Bean實例都是Proxy。
可能讀者已經想到了,既然TransactionProxyFactoryBean產生的是代理Bean,可見這種事務代理正是基于Spring AOP組件的。配置TransactionProxyFactoryBean時,一樣需要指定目標Bean。
每個TransactionProxyFactoryBean為一個目標Bean生成事務代理,事務代理的方法改寫了目標Bean的方法,就是在目標Bean的方法執行之前加入開始事務,在目標Bean的方法正常結束之前提交事務,如果遇到特定異常則回滾事務。
TransactionProxyFactoryBean創建事務代理時,需要了解當前事務所處的環境,該環境屬性通過PlatformTransactionManager實例傳入,而相關事務傳入規則在TransactionProxy- FactoryBean的定義中給出。
下面給出聲明式事務配置文件的完整代碼:
<?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,使用C3P0數據源實現 -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0.
??? ComboPooledDataSource" destroy-method="close">
??????? <!-- 指定連接數據庫的驅動 -->
??? ??? <property name="driverClass" value="com.mysql.jdbc.Driver"/>
??????? <!-- 指定連接數據庫的URL -->
??? ??? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
??????? <!-- 指定連接數據庫的用戶名 -->
??? ??? <property name="user" value="root"/>
??????? <!-- 指定連接數據庫的密碼 -->
??? ??? <property name="password" value="32147"/>
??????? <!-- 指定連接數據庫連接池的最大連接數 -->
??? ??? <property name="maxPoolSize" value="40"/>
??????? <!-- 指定連接數據庫連接池的最小連接數 -->
??? ??? <property name="minPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的初始化連接數 -->
??? ??? <property name="initialPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的連接最大空閑時間 -->
??? ??? <property name="maxIdleTime" value="20"/>
??? </bean>
??? <!-- 定義Hibernate的SessionFactory -->
??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
??? LocalSessionFactoryBean">
??????? <!-- 依賴注入SessionFactory所需的數據源,正是上文定義的dataSource -->
??????? <property name="dataSource" <ref="dataSource"/>
??????? <!-- mappingResources屬性用來列出全部映射文件 -->
??????? <property name="mappingResources">
??????????? <list>
??????????????? <!-- 以下用來列出所有的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>
??????????????? <!-- 是否根據Hiberante映射創建數據表時,選擇create、update、
??????????????? create-drop -->
??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>
??????? ???? </props>
??????? </property>
??? </bean>
??? <!-- 配置DAO Bean,該Bean將作為目標Bean使用 -->
??? <bean id="personDAOTarget" class="lee.PersonDaoImpl">
??????? <!-- 采用依賴注入來傳入SessionFactory的引用 -->
??????? <property name="sessionFactory" ref="sessionFactory"/>
??? </bean>
??? <!-- 配置Hibernate的事務管理器 -->
??? <!-- 使用HibernateTransactionManager類,該類實現PlatformTransactionManager
??? 接口,針對采用Hibernate持久化連接的特定實現 -->
??? <bean id="transactionManager"
??????? class="org.springframework.orm.hibernate3.
??????? HibernateTransactionManager">
??????? <!-- HibernateTransactionManager Bean,它需要依賴注入一個SessionFactory
??????? Bean的引用 -->
??? ??? <property name="sessionFactory" ref="sessionFactory"/>
??? </bean>
??? <!-- 配置personDAOTarget Bean的事務代理 -->
??? <bean id="personDAO"
??????? class="org.springframework.transaction.interceptor.
??????? TransactionProxyFactoryBean">
??????? <!-- 依賴注入PlatformTransactionManager的bean引用,此處使用
??????? Hibernate的bean -->
??????? <!-- 局部事務器,因此transactionManager 傳入Hibernate事務管理器的
??????? 引用 -->
??? ??? ?? <property name="transactionManager" ref="transactionManager"/>
??????? <!-- 需要生成代理的目標bean -->
??? ??? ?? <property name="target" ref="personDAOTarget"/>
??????? <!-- 指定事務屬性 -->
??? ??? ?? <property name="transactionAttributes">
??????? ??? ?? <props>
??????????????? <!-- 以下部分為定義事務回滾規則 -->
??????????? ??? ?? <prop key="insert*">PROPAGATION_REQUIRED,
??????????????? -MyCheckedException</prop>
??????????? ??? ?? <prop key="update*">PROPAGATION_REQUIRED</prop>
??????????? ??? ?? <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
??????? ??? </props>
??? ??? </property>
??? </bean>
</beans>
在上面的定義文件中,沒有對DAO對象采用Service層包裝。通常情況下,DAO層上應有一層Service層。事務代理則以Service層Bean為目標Bean。此處為了簡化配置,TransactionProxyFactoryBean直接以DAO bean作為目標bean,這一點不會影響事務代理的生成。
事務回滾規則部分定義了三個回滾規則:
第一個回滾規則表示所有以insert開始的方法,都應該滿足該事務規則。PROPAGATION_REQUIRED事務傳播規則指明,該方法必須處于事務環境中,如果當前執行線程已處于事務環境下,則直接執行;否則,啟動新的事務然后執行該方法。該規則還指定,如果方法拋出MyCheckedException的實例及其子類的實例,則強制回滾。MyCheckedException前的“-”表示強制回滾;“+”則表示強制提交,即某些情況下,即使拋出異常也強制提交;
第二個回滾規則表示所有以update開頭的方法,都遵守PROPAGATION_REQUIRED的事務傳播規則;
第三個回滾規則表示除前面規定的方法外,其他所有方法都采用PROPAGATION_ REQUIRED事務傳播規則,而且只讀。
常見的事務傳播規則有如下幾個:
?? ● PROPAGATION_MANDATORY,要求調用該方法的線程必須處于事務環境中,否則拋出異常。
?? ● PROPAGATION_NESTED,如果執行該方法的線程已處于事務環境下,依然啟動新的事務,方法在嵌套的事務里執行。如果執行該方法的線程序并未處于事務中,也啟動新的事務,然后執行該方法,此時與PROPAGATION_REQUIRED相同。
?? ● PROPAGATION_NEVER,不允許調用該方法的線程處于事務環境下,如果調用該方法的線程處于事務環境下,則拋出異常。
?? ● PROPAGATION_NOT_SUPPORTED,如果調用該方法的線程處在事務中,則先暫停當前事務,然后執行該方法。
?? ● PROPAGATION_REQUIRED,要求在事務環境中執行該方法,如果當前執行線程已處于事務中,則直接調用;如果當前執行線程不處于事務中,則啟動新的事務后執行該方法。
?? ● PROPAGATION_REQUIRES_NEW,該方法要求有一個線程在新的事務環境中執行,如果當前執行線程已處于事務中,先暫停當前事務,啟動新的事務后執行該方法;如果當前調用線程不處于事務中,則啟動新的事務后執行該方法。
?? ● PROPAGATION_SUPPORTS,如果當前執行線程處于事務中,則使用當前事務,否則不使用事務。
程序里原來使用personDAO的地方,無須變化。因為,配置文件里將personDAO目標Bean的id改成personDAOTarget,為TransactionProxyFactoryBean工廠Bean所產生的代理Bean命名為personDAO。該代理Bean會包含原有personDAO的所有方法,而且為這些方法增加了不同的事務處理規則。
程序面向PersonDaoImpl類所實現的接口編程,TransactionProxyFactoryBean生成的代理Bean也會實現TransactionProxyFactoryBean接口。因此,原有的程序中調用DAO組件的代碼無須任何改變。程序運行時,由事務代理完成原來目標Bean完成的工作。
事實上,Spring不僅支持對接口的代理,整合CGLIB后,Spring甚至可對具體類生成代理。只要設置proxyTargetClass屬性為true就可以。如果目標Bean沒有實現任何接口,proxyTargetClass屬性默認被設為true,此時Spring會對具體類生成代理。當然,通常建議面向接口編程,而不要面向具體的實現類編程。
6.3.4 使用繼承簡化事務配置
仔細觀察配置文件中兩個事務代理Bean的配置時,發現兩個事務代理Bean的大部分配置完全相同,如果配置文件中包含大量這樣的事務代理Bean配置,配置文件將非常臃腫。考慮到大部分事務代理Bean的配置都大同小異,可以使用Bean繼承來簡化事務代理的配置。
正如前面部分介紹的,Bean繼承是將所有子Bean中相同的配置定義成一個模板,并將此模板Bean定義成一個抽象Bean。考慮所有事務代理Bean中,有如下部分是大致相?? 同的:
?? ● 事務代理Bean所使用的事務管理器。
?? ● 事務傳播規則。
因此,現在配置文件中定義如下的事務代理模板Bean,其配置代碼如下:
<!-- 定義所有事務代理Bean的模板 -->
<bean id="txProxyTemplate" abstract="true"
??????? class="org.springframework.transaction.interceptor.
??????? TransactionProxyFactoryBean">
??? <!-- 為事務代理Bean注入生成代理所需的PlatformTransactionManager實例 -->
??? <property name="transactionManager" ref="transactionManager"/>
??????? <!-- 定義生成事務代理通用的事務屬性 -->
??????? <property name="transactionAttributes">
??????????? <props>
??????????????? <!-- 所有的方法都應用PROPAGATION_REQUIRED的事務傳播規則 -->
??????????????? <prop key="*">PROPAGATION_REQUIRED</prop>
??????????? </props>
??? </property>
</bean>
而真正的事務代理Bean,則改為繼承上面的事務模板Bean。考慮到將目標Bean定義在Spring容器中可能增加未知的風險,因此將目標Bean定義成嵌套Bean。
<!-- 讓事務代理Bean繼承模板Bean -->
<bean id="personDAO" parent="txProxyTemplate">
??? <!-- 這里采用嵌套Bean的方式來定義目標Bean,當然也可以引用已存在的Bean -->
??? <property name="target">
??????? <bean class="lee.personDAO"/>
??? </property>
</bean>
此時的personDAO Bean無須具體地定義事務屬性,它將在其父Bean txProxyTemplate中獲取事務定義屬性。此處采用嵌套Bean來定義目標Bean,因此,并未將目標Bean直接暴露在Spring的上下文中讓其他模塊調用。當然,也可采用一個已經存在的Bean作為目標Bean;子Bean的事務屬性定義,完全可覆蓋事務代理模板里的事務屬性定義。如下例所示:
<!-- 讓事務代理bean繼承模板Bean -->
<bean id="personDAO" parent="txProxyTemplate">
??? <!-- 這里,采用引用已存在的bean的方式來定義目標Bean -->
??? <property name="target" ref ="personDAOTarget"/>
??? <!-- 覆蓋事務代理模板bean中的事務屬性定義 -->
??? <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>
可見,采用Bean繼承方式定義事務代理的方式,可以很好地簡化事務代理的配置,可以避免配置事務代理Bean時的冗余配置。
提示:使用Bean繼承可以很好地簡化事務代理Bean的配置,通過將各事務代理Bean共同的配置信息提取成事務模板Bean,可以讓實際的事務代理Bean的配置更加簡潔;而且,配置方式相當直觀。盡量將目標Bean配置成嵌套Bean,這樣的方式可以保證更好的內聚性。
如果讀者還記得前面介紹的AOP知識,應該知道還有一種更加簡潔的配置,就是利用Bean后處理器,讓Bean后處理器為容器中其他Bean自動創建事務代理。
6.3.5 使用自動創建代理簡化事務配置
回顧6.2.6節和6.2.7節,讀者可能已經想到如何自動創建代理。是的,正是通過6.2.6節和6.2.7節所給出的兩個自動代理創建類來生成事務代理。
正如前文已經提到的,使用BeanNameAutoProxyCreator和DefaultAdvisorAutoProxy- Creator來創建代理時,并不一定是創建事務代理,關鍵在于傳入的攔截器,如果傳入事務攔截器,將可自動生成事務代理。
下面是使用BeanNameAutoProxyCreator自動生成事務代理的配置文件:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定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,使用C3P0數據源實現 -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0.
??? ComboPooledDataSource" destroy-method="close">
??????? <!-- 指定連接數據庫的驅動 -->
??????? <property name="driverClass" value="com.mysql.jdbc.Driver"/>
??????? <!-- 指定連接數據庫的URL -->
??????? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
??????? <!-- 指定連接數據庫的用戶名 -->
??????? <property name="user" value="root"/>
??????? <!-- 指定連接數據庫的密碼 -->
??????? <property name="password" value="32147"/>
??????? <!-- 指定連接數據庫連接池的最大連接數 -->
??????? <property name="maxPoolSize" value="40"/>
??????? <!-- 指定連接數據庫連接池的最小連接數 -->
??????? <property name="minPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的初始化連接數 -->
??????? <property name="initialPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的連接最大空閑時間 -->
??????? <property name="maxIdleTime" value="20"/>
??? </bean>
??? <!-- 使用JDBC的局部事務策略 -->
??? <bean id="transactionManager"
??????? class="org.springframework.jdbc.datasource.DataSource-
??????? TransactionManager">
??????? <!-- 為事務管理器注入所需的數據源Bean -->
??????? <property name="dataSource" ref="dataSource"/>
??? </bean>
??? <!-- 配置目標Bean,該目標Bean將由Bean后處理器自動生成代理 -->
??? <bean id="test1" class="lee.TransactionTestImpl">
??????? <!-- 依賴注入目標Bean所必需的數據源Bean -->
??? ??? <property name="ds" ref="dataSource"/>
??? </bean>
??? <!-- 配置目標Bean,該目標Bean將由Bean后處理器自動生成代理 -->
??? <bean id="test2" class="lee.TestImpl">
??????? <!-- 依賴注入目標Bean所必需的數據源Bean -->
??? ??? <property name="ds" ref="dataSource"/>
??? </bean>
??? <!-- 配置事務攔截器Bean -->
??? <bean id="transactionInterceptor"
??????? class="org.springframework.transaction.interceptor.
??????? TransactionInterceptor">
??????? <!-- 事務攔截器bean需要依賴注入一個事務管理器 -->
??????? <property name="transactionManager" ref="transactionManager"/>
??????? <property name="transactionAttributes">
??????????? <!-- 下面定義事務傳播屬性 -->
??????????? <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">
??? <!-- 指定對滿足哪些bean name的bean自動生成業務代理 -->
??? ??? <property name="beanNames">
??????????? <!-- 下面是所有需要自動創建事務代理的Bean -->
??????????? <list>
??????????????? <value>test1</value>
??????????????? <value>test2</value>
??????????? </list>
??????????? <!-- 此處可增加其他需要自動創建事務代理的Bean -->
??? ??? </property>
??????? <!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->
??????? <property name="interceptorNames">
??????????? <list>
??????????????? <value>transactionInterceptor</value>
??????????????? <!-- 此處可增加其他新的Interceptor -->
??????????? </list>
??????? </property>
??? </bean>
</beans>
如果配置文件中僅有兩個目標Bean,可能不能很清楚地看出這種自動創建代理配置方式的優勢,但如果有更多目標Bean需要自動創建事務代理,則可以很好地體會到這種配置方式的優勢:配置文件只需要簡單地配置目標Bean,然后在BeanNameAutoProxyCreator配置中增加一行即可。
提示:使用BeanNameAutoProxyCreator可以自動創建事務代理,使用DefaultAdvisor- AutoProxyCreator也可自動創建事務代理。關于后一個Bean后處理器的配置方式,請參看前面6.2.7節的內容。
posted @
2009-07-19 10:18 jadmin 閱讀(80) |
評論 (0) |
編輯 收藏
6.3 Spring的事務
Spring的事務管理不需與任何特定的事務API耦合。對不同的持久層訪問技術,編程式事務提供一致的事務編程風格,通過模板化的操作一致性地管理事務。聲明式事務基于Spring AOP實現,卻并不需要程序開發者成為AOP專家,亦可輕易使用Spring的聲明式事務管理。
6.3.1 Spring支持的事務策略
Spring事務策略是通過PlatformTransactionManager接口體現的,該接口是Spring事務策略的核心。該接口的源代碼如下:
public interface PlatformTransactionManager
{
??? //平臺無關的獲得事務的方法
??? TransactionStatus getTransaction(TransactionDefinition definition)
??????? throws TransactionException;
??? //平臺無關的事務提交方法
??? void commit(TransactionStatus status) throws TransactionException;
??? //平臺無關的事務回滾方法
??? void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager是一個與任何事務策略分離的接口,隨著底層不同事務策略切換,應用必須采用不同的實現類。PlatformTransactionManager接口沒有與任何事務資源捆綁在一起,它可以適應于任何的事務策略,結合Spring的IoC容器,可以向PlatformTransactionManager注入相關的平臺特性。
PlatformTransactionManager接口有許多不同的實現類,應用程序面向與平臺無關的接口編程,對不同平臺的底層支持,由PlatformTransactionManager接口的實現類完成。從而,應用程序無須與具體的事務API耦合。因此,使用PlatformTransactionManager接口,可將代碼從具體的事務API中解耦出來。
即使使用特定容器管理的JTA,代碼依然無須執行JNDI查找,無須與特定的JTA資源耦合在一起。通過配置文件,JTA資源傳給PlatformTransactionManager的實現類。因此,程序的代碼可在JTA事務管理和非JTA事務管理之間輕松切換。
在PlatformTransactionManager接口內,包含一個getTransaction(TransactionDefinition definition)方法,該方法根據一個TransactionDefinition參數,返回一個TransactionStatus對象。TransactionStatus對象表示一個事務。TransactionStatus被關聯在當前執行的線程。
getTransaction(TransactionDefinition definition)返回的TransactionStatus對象,可能是一個新的事務,也可能是一個已經存在的事務對象。如果當前執行的線程已經處于事務管理下,返回當前線程的事務對象,否則,返回當前線程的調用堆棧已有的事務對象。
TransactionDefinition接口定義了一個事務規則,該接口必須指定如下幾個屬性值:
?? ● 事務隔離,當前事務和其他事務的隔離程度。例如,這個事務能否看到其他事務未提交的數據等。
?? ● 事務傳播,通常,在事務中執行的代碼都會在當前事務中運行。但是,如果一個事務上下文已經存在,有幾個選項可指定該事務性方法的執行行為。例如,大多數情況下,簡單地在現有的事務上下文中運行;或者掛起現有事務,創建一個新的事務。Spring提供EJB CMT(Contain Manager Transaction,容器管理事務)中所有的事務傳播選項。
?? ● 事務超時,事務在超時前能運行多久。事務的最長持續時間。如果事務一直沒有被提交或回滾,將在超出該時間后,系統自動回滾事務。
?? ● 只讀狀態,只讀事務不修改任何數據。在某些情況下(例如使用Hibernate時),只讀事務是非常有用的優化。
TransactionStatus代表事務本身,它提供了簡單的控制事務執行和查詢事務狀態的方法。這些方法在所有的事務API中都是相同的。TransactionStatus接口的源代碼如下:
public interface TransactionStatus
{
??? //判斷事務是否是新建的事務
??? boolean isNewTransaction();
??? //設置事務回滾
??? void setRollbackOnly();
??? //查詢事務是否已有回滾標志
??? boolean isRollbackOnly();
}
Spring的事務管理由PlatformTransactionManager的不同實現類完成。在Spring上下文中配置PlatformTransactionManager Bean時,必須針對不同環境提供不同的實現類。
下面提供不同的持久層訪問環境,及其對應的PlatformTransactionManager實現類的 配置。
JDBC數據源的局部事務策略:
<?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,使用C3P0數據源實現 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
??? <!-- 指定連接數據庫的驅動 -->
??? <property name="driverClass" value="com.mysql.jdbc.Driver"/>
??? <!-- 指定連接數據庫的URL -->
??? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
??? <!-- 指定連接數據庫的用戶名 -->
??? <property name="user" value="root"/>
??? <!-- 指定連接數據庫的密碼 -->
??? <property name="password" value="32147"/>
??? <!-- 指定連接數據庫連接池的最大連接數 -->
??? <property name="maxPoolSize" value="40"/>
??? <!-- 指定連接數據庫連接池的最小連接數 -->
??? <property name="minPoolSize" value="1"/>
??? <!-- 指定連接數據庫連接池的初始化連接數 -->
??? <property name="initialPoolSize" value="1"/>
??? <!-- 指定連接數據庫連接池的連接最大空閑時間 -->
??? <property name="maxIdleTime" value="20"/>
</bean>
<!-- 配置JDBC數據源的局部事務管理器 -->
<!-- 使用DataSourceTransactionManager 類,該類實現PlatformTransactionManager接口 -->
<!-- 針對采用數據源連接的特定實現 -->
<bean id="transactionManager"
??????? class="org.springframework.jdbc.datasource.
??????? DataSourceTransactionManager">
??????? <!-- DataSourceTransactionManager bean需要依賴注入一個DataSource
??????? bean的引用 -->
??? ???? <property name="dataSource" ref="dataSource"/>
??? </bean>
</beans>
對于容器管理JTA數據源,全局事務策略的配置文件如下:
<?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數據源Bean -->
??? <bean id="dataSource" class="org.springframework.jndi.
??? JndiObjectFactoryBean">
??? <!-- 容器管理數據源的JNDI -->
??? ???? <property name="jndiName" value="jdbc/jpetstore"/>
??? </bean>
??? <!-- 使用JtaTransactionManager類,該類實現PlatformTransactionManager接
??? 口 -->
??? <!-- 針對采用全局事務管理的特定實現 -->
??? <!-- JtaTransactionManager不需要知道數據源,或任何其他特定資源 -->
??? <!-- 因為它使用容器的全局事務管理 -->
??? <bean id="transactionManager"
??????? class="org.springframework.transaction.jta.
??????? JtaTransactionManager" />
</beans>
對于采用Hibernate持久層訪問策略時,局部事務策略的配置文件如下:
<?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,使用C3P0數據源實現 -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0.
??? ComboPooledDataSource" destroy-method="close">
??????? <!-- 指定連接數據庫的驅動 -->
??? ??? <property name="driverClass" value="com.mysql.jdbc.Driver"/>
??????? <!-- 指定連接數據庫的URL -->
??? ??? <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
??????? <!-- 指定連接數據庫的用戶名 -->
??? ??? <property name="user" value="root"/>
??????? <!-- 指定連接數據庫的密碼 -->
??? ??? <property name="password" value="32147"/>
??????? <!-- 指定連接數據庫連接池的最大連接數 -->
??? ??? <property name="maxPoolSize" value="40"/>
??????? <!-- 指定連接數據庫連接池的最小連接數 -->
??? ??? <property name="minPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的初始化連接數 -->
??? ??? <property name="initialPoolSize" value="1"/>
??????? <!-- 指定連接數據庫連接池的連接最大空閑時間 -->
??? ??? <property name="maxIdleTime" value="20"/>
??? </bean>
??? <!-- 定義Hibernate的SessionFactory -->
??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
??? LocalSessionFactoryBean">
??????? <!-- 依賴注入SessionFactory所需的數據源,正是上文定義的dataSource -->
??????? <property name="dataSource" ref="dataSource"/>
??????? <!-- mappingResources屬性用來列出全部映射文件 -->
??????? <property name="mappingResources">
??????????? <list>
??????????????? <!-- 以下用來列出所有的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>
??????????????? <!-- 是否根據Hibernate映射創建數據表時,選擇create、update、
??????????????? create-drop -->
??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>
??????? ???? </props>
??????? </property>
??? </bean>
??? <!-- 配置Hibernate的局部事務管理器 -->
??? <!-- 使用HibernateTransactionManager類,該類是PlatformTransactionManager
??? 接口,針對采用Hibernate持久化連接的特定實現 -->
??? <bean id="transactionManager"
??? class="org.springframework.orm.hibernate3.
??? HibernateTransactionManager">
??????????? <!-- HibernateTransactionManager Bean需要依賴注入一個
??????????? SessionFactorybean的引用 -->
??? ???? <property name="sessionFactory" ref="sessionFactory"/>
???? </bean>
</beans>
對于采用Hibernate持久層訪問策略時,全局事務策略的配置文件如下:
<?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數據源Bean -->
??? <bean id="dataSource" class="org.springframework.jndi.
??? JndiObjectFactoryBean">
??????? <!-- 容器管理數據源的JNDI -->
??? ???? <property name="jndiName" value="jdbc/jpetstore"/>
??? </bean>
??? <!--定義Hibernate的SessionFactory -->
??? <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
??? LocalSessionFactoryBean">
??????? <!-- 依賴注入SessionFactory所需的數據源,正是上文定義的dataSource Bean -->
??????? <property name="dataSource" ref="dataSource"/>
??????? <!-- mappingResources屬性用來列出全部映射文件 -->
??????? <property name="mappingResources">
??????????? <list>
??? ????????????? <!-- 以下用來列出所有的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>
??????????????? <!-- 是否根據Hiberante映射創建數據表時,選擇create、update、
??????????????? create-drop -->
??????????? ????? <prop key="hibernate.hbm2ddl.auto">update</prop>
??????? ???? </props>
???? ???? </property>
??? </bean>
??? <!-- 使用JtaTransactionManager類,該類是PlatformTransactionManager接口,
??????????? 針對采用數據源連接的特定實現 -->
??? <!-- JtaTransactionManager不需要知道數據源,或任何其他特定資源,
??????????? 因為使用容器的全局事務管理 -->
??? <bean id="transactionManager"
??????? ?? class="org.springframework.transaction.jta.
??????? ?? JtaTransactionManager" />
</beans>
不論采用哪種持久層訪問技術,只要使用JTA數據源,Spring事務管理器的配置都是一樣的,因為它們都采用的是全局事務管理。
可以看到,僅僅通過配置文件的修改,就可以在不同的事務管理策略間切換,即使從局部事務到全局事務的切換。
提示:Spring所支持的事務策略非常靈活,Spring的事務策略允許應用程序在不同事務策略之間自由切換,即使需要在局部事務策略和全局事務策略之間切換,只需要修改配置文件,而應用程序的代碼無須任何改變。這種靈活的設計,又何嘗不是因為面向接口編程帶來的優勢,可見面向接口編程給應用程序更好的適應性。
posted @
2009-07-19 10:18 jadmin 閱讀(76) |
評論 (0) |
編輯 收藏
6.2.4 代理接口
當目標Bean的實現類實現了接口后,Spring AOP可以為其創建JDK動態代理,而無須使用CGLIB創建的代理,這種代理稱為代理接口。
創建AOP代理必須指定兩個屬性:目標Bean和處理。實際上,很多AOP框架都以攔截器作為處理。因為Spring AOP與IoC容器的良好整合,因此配置代理Bean時,完全可以利用依賴注入來管理目標Bean和攔截器Bean。
下面的示例演示了基于AOP的權限認證,它是簡單的TestService接口,該接口模擬Service組件,該組件內包含兩個方法:
?? ● 查看數據。
?? ● 修改數據。
接口的源代碼如下:
//Service組件接口
public interface TestService
{
??? //查看數據
??? void view();
??? //修改數據
??? void modify();
}
該接口的實現類實現兩個方法。因為篇幅限制,本示例并未顯示出完整的查看數據和修改數據的持久層操作,僅僅在控制臺打印兩行信息。實際的項目實現中,兩個方法的實現則改成對持久層組件的調用,這不會影響示例程序的效果。實現類的源代碼如下:
TestService接口的實現類
public class TestServiceImpl implements TestService
{
??? //實現接口必須實現的方法
??? public void view()
??? {
??????? System.out.println("用戶查看數據");
??? }
??? //實現接口必須實現的方法
??? public void modify()
??? {
??????? System.out.println("用戶修改數據");
??? }
}
示例程序采用Around 處理作為攔截器,攔截器中使用依賴注入獲得當前用戶名。實際Web應用中,用戶應該從session中讀取。這不會影響示例代碼的效果。攔截器源代碼如下:
public class AuthorityInterceptor implements MethodInterceptor
{
??? //當前用戶名
??? private String user;
??? //依賴注入所必需的setter方法
??? public void setUser(String user)
??? {
??????? this.user = user;
??? }
??? public Object invoke(MethodInvocation invocation) throws Throwable
??? {
??????? //獲取當前攔截的方法名
??????? String methodName = invocation.getMethod().getName();
??????? //下面執行權限檢查
??????? //對既不是管理員,也不是注冊用戶的情況
??????? if (!user.equals("admin") && !user.equals("registedUser"))
??????? {
??????????? System.out.println("您無權執行該方法");
??????????? return null;
??????? }
??????? //對僅僅是注冊用戶,調用修改數據的情況
??????? else if (user.equals("registedUser") && methodName.equals
??????? ("modify"))
??????? {
??????????? System.out.println("您不是管理員,無法修改數據");
??????????? return null;
??????? }
??????? //對管理員或注冊用戶,查看數據的情況
??????? else
??????? {
??????????? return invocation.proceed();
??????? }
??? }
}
TestAction類依賴TestService。因篇幅關系,此處不給出TestAction的接口的源代碼,TestActionImpl的源代碼如下:
public class TestActionImpl
{
??? //將TestService作為成員變量,面向接口編程
??? private TestService ts;
??? //依賴注入的setter方法
??? public void setTs(TestService ts)
??? {
??????? this.ts = ts;
??? }
??? //修改數據
??? public void modify()
??? {
??????? ts.modify();
??? }
??? //查看數據
??? 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">
??? <!-- 配置目標Bean -->
??? <bean id="serviceTarget" class="lee.TestServiceImpl"/>
??? <!-- 配置攔截器,攔截器作為處理使用 -->
??? <bean id="authorityInterceptor" class="lee.AuthorityInterceptor">
??????? <property name="user" value="admin"/>
??? </bean>
??? <!-- 配置代理工廠Bean,負責生成AOP代理 -->
??? <bean id="service" class="org.springframework.aop.framework.
??? ProxyFactoryBean">
??????? <!-- 指定AOP代理所實現的接口 -->
??????? <property name="proxyInterfaces" value="lee.TestService"/>
??????? <!-- 指定AOP代理所代理的目標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>
主程序請求testAction Bean,然后調用該Bean的兩個方法,主程序如下:
public class BeanTest
{
??? public static void main(String[] args)throws Exception
??? {
??????? //創建Spring容器實例
??????? ApplicationContext ctx = new FileSystemXmlApplicationContext
??????? ("bean.xml");
??????? //獲得TestAction bean
??????? TestAction ta = (TestAction)ctx.getBean("testAction");
??????? //調用bean的兩個測試方法
??????? ta.view();
??????? ta.modify();
??? }
}
程序執行結果如下:
[java] 用戶查看數據
[java] 用戶修改數據
代理似乎沒有發揮任何作用。因為配置文件中的當前用戶是admin,admin用戶具備訪問和修改數據的權限,因此代理并未阻止訪問。將配置文件中的admin修改成registed- User,再次執行程序,得到如下結果:
[java] 用戶查看數據
[java] 您不是管理員,無法修改數據
代理阻止了registedUser修改數據,查看數據可以執行。將registedUser修改成其他用戶,執行程序,看到如下結果:
[java] 您無權執行該方法
[java] 您無權執行該方法
代理阻止用戶對兩個方法的執行。基于AOP的權限檢查,可以降低程序的代碼量,因為無須每次調用方法之前,手動編寫權限檢查代碼;同時,權限檢查與業務邏輯分離,提高了程序的解耦。
示例中的目標Bean被暴露在容器中,可以被客戶端代碼直接訪問。為了避免客戶端代碼直接訪問目標Bean,可以將目標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將負責創建目標Bean的代理 -->
??? <bean id="service" class="org.springframework.aop.framework.
??? ProxyFactoryBean">
??????? <!-- 指定AOP代理所實現的接口 -->
??????? <property name="proxyInterfaces" value="lee.TestService"/>
??????? <property name="target">
??????????? <!-- 以嵌套Bean的形式定義目標Bean,避免客戶端直接訪問目標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>
由上面介紹的內容可見,Spring的AOP是對JDK動態代理模式的深化。通過Spring AOP組件,允許通過配置文件管理目標Bean和AOP所需的處理。
下面將繼續介紹如何為沒有實現接口的目標Bean創建CGLIB代理。
6.2.5 代理類
如果目標類沒有實現接口,則無法創建JDK動態代理,只能創建CGLIB代理。如果需要沒有實現接口的Bean實例生成代理,配置文件中應該修改如下兩項:
?? ● 去掉<property name="proxyInterfaces"/>聲明。因為不再代理接口,因此,此處的配置沒有意義。
?? ● 增加<property name="proxyTargetClass">子元素,并設其值為true,通過該元素強制使用CGLIB代理,而不是JDK動態代理。
注意:最好面向接口編程,不要面向類編程。同時,即使在實現接口的情況下,也可強制使用CGLIB代理。
CGLIB代理在運行期間產生目標對象的子類,該子類通過裝飾器設計模式加入到Advice中。因為CGLIB代理是目標對象的子類,則必須考慮保證如下兩點:
?? ● 目標類不能聲明成final,因為final類不能被繼承,無法生成代理。
?? ● 目標方法也不能聲明成final,final方法不能被重寫,無法得到處理。
當然,為了需要使用CGLIB代理,應用中應添加CGLIB二進制Jar文件。
6.2.6 使用BeanNameAutoProxyCreator自動創建代理
這是一種自動創建事務代理的方式,一旦在容器中配置了BeanNameAutoProxyCreator實例,該實例將會對指定名字的Bean實例自動創建代理。實際上,BeanNameAutoProxyCreator是一個Bean后處理器,理論上它會對容器中所有的Bean進行處理,實際上它只對指定名字的Bean實例創建代理。
BeanNameAutoProxyCreator根據名字自動生成事務代理,名字匹配支持通配符。
與ProxyFactoryBean一樣,BeanNameAutoProxyCreator需要一個interceptorNames屬性,該屬性名雖然是“攔截器”,但并不需要指定攔截器列表,它可以是Advisor或任何處理類型。
下面是使用BeanNameAutoProxyCreator的配置片段:
<!-- 定義事務攔截器bean -->
<bean id="transactionInterceptor"
??? class="org.springframework.transaction.interceptor.
??? TransactionInterceptor">
??? <!-- 事務攔截器bean需要依賴注入一個事務管理器 -->
??? <property name="transactionManager" ref="transactionManager"/>
??? <property name="transactionAttributes">
??????? <!-- 下面定義事務傳播屬性 -->
??????? <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后處理器,
??????? 負責為容器中特定的Bean創建AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy.
??? BeanNameAutoProxyCreator">
??? <!-- 指定對滿足哪些bean name的bean自動生成業務代理 -->
??? <property name="beanNames">
??????? <list>
??????????? <!-- 下面是所有需要自動創建事務代理的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>
??????????? <!-- 此處可增加其他需要自動創建事務代理的Bean -->
??????? </list>
??? </property>
??? <!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->
??? <property name="interceptorNames">
??????? <list>
??????????? <value>transactionInterceptor</value>
??????????? <!-- 此處可增加其他新的Interceptor -->
??????? </list>
??? </property>
</bean>
上面的片段是使用BeanNameAutoProxyCreator自動創建事務代理的片段。Transaction- Interceptor用來定義事務攔截器,定義事務攔截器時傳入事務管理器Bean,也指定事務傳播屬性。
通過BeanNameAutoProxyCreator定義Bean后處理器,定義該Bean后處理器時,通過beanNames屬性指定有哪些目標Bean生成事務代理;還需要指定“攔截器鏈”,該攔截器鏈可以由任何處理組成。
定義目標Bean還可使用通配符,使用通配符的配置片段如下所示:
<!-- 定義BeanNameAutoProxyCreator Bean,它是一個Bean后處理器,
??????? 負責為容器中特定的Bean創建AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
??? <!-- 指定對滿足哪些bean name的bean自動生成業務代理 -->
??? <property name="beanNames">
??????? <!-- 此處使用通配符確定目標bean -->
??????? <value>*DAO,*Service,*Manager</value>
??? </property>
??? <!-- 下面定義BeanNameAutoProxyCreator所需的事務攔截器 -->
??? <property name="interceptorNames">
??????? <list>
??????????? <value>transactionInterceptor</value>
??????????? <!-- 此處可增加其他新的Interceptor -->
??????? </list>
??? </property>
</bean>
上面的配置片段中,所有名字以DAO、Service、Manager結尾的bean,將由該“bean后處理器”為其創建事務代理。目標bean不再存在,取而代之的是目標bean的事務代理。通過這種方式,不僅可以極大地降低配置文件的繁瑣,而且可以避免客戶端代碼直接調用目標bean。
注意:雖然上面的配置片段是為目標對象自動生成事務代理。但這不是唯一的,如果有需要,可以為目標對象生成任何的代理。BeanNameAutoProxyCreator為目標對象生成怎樣的代理,取決于傳入怎樣的處理Bean,如果傳入事務攔截器,則生成事務代理Bean;否則將生成其他代理,在后面的實例部分,讀者將可以看到大量使用BeanNameAutoProxy- Creator創建的權限檢查代理。
6.2.7 使用DefaultAdvisorAutoProxyCreator自動創建代理
Spring還提供了另一個Bean后處理器,它也可為容器中的Bean自動創建代理。相比之下,DefaultAdvisorAutoProxyCreator是更通用、更強大的自動代理生成器。它將自動應用于當前容器中的advisor,不需要在DefaultAdvisorAutoProxyCreator定義中指定目標Bean的名字字符串。
這種定義方式有助于配置的一致性,避免在自動代理創建器中重復配置目標Bean 名。
使用該機制包括:
?? ● 配置DefaultAdvisorAutoProxyCreator bean定義。
?? ● 配置任何數目的Advisor,必須是Advisor,不僅僅是攔截器或其他處理。因為,必須使用切入點檢查處理是否符合候選Bean定義。
DefaultAdvisorAutoProxyCreator計算Advisor包含的切入點,檢查處理是否應該被應用到業務對象,這意味著任何數目的Advisor都可自動應用到業務對象。如果Advisor中沒有切入點符合業務對象的方法,這個對象就不會被代理。如果增加了新的業務對象,只要它們符合切入點定義,DefaultAdvisorAutoProxyCreator將自動為其生成代理,無須額外?? 配置。
當有大量的業務對象需要采用相同處理,DefaultAdvisorAutoProxyCreator是非常有用的。一旦定義恰當,直接增加業務對象,而不需要額外的代理配置,系統自動為其增加???? 代理。
<beans>
??? <!-- 定義Hibernate局部事務管理器,可切換到JTA全局事務管理器 -->
??? <bean id="transactionManager"
???????? class="org.springframework.orm.hibernate3.
??????? HibernateTransactionManager">
??????? <!-- 定義事務管理器時,依賴注入SessionFactory -->
???????? <property name="sessionFactory" ref bean="sessionFactory"/>
??? </bean>
??? <!-- 定義事務攔截器 -->
??? <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,這是一個Bean后處理器 -->
??? <bean class="org.springframework.aop.framework.autoproxy.
??? DefaultAdvisorAutoProxyCreator"/>
??? <!-- 定義事務Advisor -->
??? <bean class="org.springframework.transaction.interceptor.
??? TransactionAttributeSourceAdvisor">
??? ???? <property name="transactionInterceptor" ref=
??????? "transactionInterceptor"/>
??? </bean>
??? <!-- 定義額外的Advisor>
??? <bean id="customAdvisor" class="lee.MyAdvisor"/>
</beans>
DefaultAdvisorAutoProxyCreator支持過濾和排序。如果需要排序,可讓Advisor實現org.springframework.core.Ordered接口來確定順序。TransactionAttributeSourceAdvisor已經實現Ordered接口,因此可配置其順序,默認是不排序。
采用這樣的方式,一樣可以避免客戶端代碼直接訪問目標Bean,而且配置更加簡潔。只要目標Bean符合切入點檢查,Bean后處理器自動為目標Bean創建代理,無須依次指定目標Bean名。
注意:Spring也支持以編程方式創建AOP代理,但這種方式將AOP代理所需要的目標對象和處理Bean等對象的耦合降低到代碼層次,因此不推薦使用。如果讀者需要深入了解如何通過編程方式創建AOP代理,請參閱筆者所著的《Spring2.0寶典》。
posted @
2009-07-19 10:16 jadmin 閱讀(93) |
評論 (0) |
編輯 收藏
6.2 Spring的AOP
AOP(Aspect Orient Programming),也就是面向切面編程,作為面向對象編程的一種補充。問世的時間并不太長,甚至在國內的翻譯還不太統一(有些書翻譯成面向方面編程),但它確實極好地補充了面向對象編程的方式。面向對象編程將程序分解成各個層次的對象,而面向切面編程將程序運行過程分解成各個切面。
可以這樣理解,面向對象編程是從靜態角度考慮程序結構,面向切面編程是從動態角度考慮程序運行過程。
Spring AOP是Spring框架的一個重要組件,極好地補充了Spring IoC容器的功能。Spring AOP將Spring IoC容器與AOP組件緊密結合,豐富了IoC容器的功能。當然,即使不使用AOP組件,依然可以使用Spring的IoC容器。
6.2.1 AOP的基本概念
AOP從程序運行角度考慮程序的流程,提取業務處理過程的切面。AOP面向的是程序運行中各個步驟,希望以更好的方式來組合業務處理的各個步驟。
AOP框架并不與特定的代碼耦合,AOP框架能處理程序執行中的特定點,而不是某個具體的程序。AOP框架具有如下兩個特征:
?? ● 各步驟之間的良好隔離性。
?? ● 源代碼無關性。
下面是關于面向切面編程的一些術語:
?? ● 切面,業務流程運行的某個特定步驟,就是運行過程的關注點,關注點可能橫切多個對象。
?? ● 連接點,程序執行過程中明確的點,如方法的調用或異常的拋出。Spring AOP中,連接點總是方法的調用,Spring并沒有顯式地使用連接點。
?? ● 處理(Advice),AOP框架在特定的連接點執行的動作。處理有around、before和throws等類型。大部分框架都以攔截器作為處理模型。
?? ● 切入點,系列連接點的集合,它確定處理觸發的時機。AOP框架允許開發者自己定義切入點,如使用正則表達式。
?? ● 引入,添加方法或字段到被處理的類。Spring允許引入新的接口到任何被處理的對象。例如,可以使用一個引入,使任何對象實現IsModified接口,以此來簡化緩存。
?? ● 目標對象,包含連接點的對象。也稱為被處理對象或被代理對象。
?? ● AOP代理,AOP框架創建的對象,包含處理。簡單地說,代理就是對目標對象的加強。Spring中的AOP代理可以是JDK動態代理,也可以是CGLIB代理。前者為實現接口的目標對象的代理,后者為不實現接口的目標對象的代理。
注意:面向切面編程是比較前沿的知識,而國內大部分翻譯人士翻譯計算機文獻時,總是一邊開著各種詞典和翻譯軟件,一邊逐詞去看文獻,不是先從總體上把握知識的架構。因此,難免導致一些術語的翻譯詞不達意,例如,Socket被翻譯成“套接字”等。在面向切面編程的各術語翻譯上,也存在較大的差異。對于Advice一詞,有翻譯為“通知”的,有翻譯為“建議”的,如此種種,不一而足。實際上,Advice指AOP框架在特定切面所做的事情,故而筆者翻譯為“處理”,希望可以表達Advice的真正含義。
6.2.2 AOP的代理
所謂AOP代理,就是AOP框架動態創建的對象,這個對象通常可以作為目標對象的替代品,而AOP代理提供比目標對象更加強大的功能。真實的情形是,當應用調用AOP代理的方法時,AOP代理會在自己的方法中回調目標對象的方法,從而完成應用的調用。
關于AOP代理的典型例子就是Spring中的事務代理Bean。通常,目標Bean的方法不是事務性的,而AOP代理包含目標Bean的全部方法,而且這些方法經過加強變成了事務性方法。簡單地說,目標對象是藍本,AOP代理是目標對象的加強,在目標對象的基礎上,增加屬性和方法,提供更強大的功能。
目標對象包含一系列切入點。切入點可以觸發處理連接點集合。用戶可以自己定義切入點,如使用正則表達式。AOP代理包裝目標對象,在切入點處加入處理。在切入點加入的處理,使得目標對象的方法功能更強。
Spring默認使用JDK動態代理實現AOP代理,主要用于代理接口。也可以使用CGLIB代理。實現類的代理,而不是接口。如果業務對象沒有實現接口,默認使用CGLIB代理。但面向接口編程是良好的習慣,盡量不要面向具體類編程。因此,業務對象通常應實現一個或多個接口。
下面是一個簡單動態代理模式的示例,首先有一個Dog的接口,接口如下:
public interface Dog
{
??? //info方法聲明
??? public void info();
??? //run方法聲明
??? public void run();
}
然后,給出該接口的實現類,實現類必須實現兩個方法,源代碼如下:
public class DogImpl implements Dog
{
??? //info方法實現,僅僅打印一個字符串
??? public void info()
??? {
??????? System.out.println("我是一只獵狗");
??? }
??? //run方法實現,僅僅打印一個字符串
??? public void run()
??? {
??????? System.out.println("我奔跑迅速");
??? }
}
上面的代碼沒有絲毫獨特之處,是典型的面向接口編程的模型,為了有更好的解耦,采用工廠來創建Dog實例。工廠源代碼如下:
public class DogFactory
{
??? //工廠本身是單態模式,因此,將DogFactory作為靜態成員變量保存
??? private static DogFactory df;
??? //將Dog實例緩存
??? private Dog gundog;
??? //默認的構造器,單態模式需要的構造器是private
??? private DogFactory()
??? {
??? }
??? //單態模式所需的靜態方法,該方法是創建本類實例的唯一方法點
??? public static DogFactory instance()
??? {
??????? if (df == null)
??????? {
??????????? df = new DogFactory();
??????? }
??????? return df;
??? }
??? //獲得Dog實例
??? public Dog getDog(String dogName)
??? {
??????? //根據字符串參數決定返回的實例
??????? if (dogName.equals("gundog"))
??????? {
??????????? //返回Dog實例之前,先判斷緩存的Dog是否存在,如果不存在才創建,
??????????? 否則直接返回緩存的Dog實例
??????????? if (gundog == null )
??????????? {
??????????????? gundog = new DogImpl();
??????????? }
??????????? return gundog;
??? ???? }
??????? return null;
??? }
}
下面是一個通用的處理類,該處理類沒有與任何特定的類耦合,它可以處理所有的目標對象。從JDK 1.3起,Java的import java.lang.reflect下增加InvocationHandler接口,該接口是所有處理類的根接口。
該類處理類的源代碼如下:
public class ProxyHandler implements InvocationHandler
{
??? //需被代理的目標對象
??? private Object target;
??? //執行代理的目標方法時,該invoke方法會被自動調用
??? public Object invoke(Object proxy, Method method, Object[] args)throws
??? Exception
??? {
??????? Object result = null;
??????? if (method.getName().equals("info"))
??????? {
??????????? System.out.println("======開始事務...");
??????????? result =method.invoke(target, args);
??????????? System.out.println("======提交事務...");
??????? }
??????? else
??????? {
??????????? result =method.invoke(target, args);
??????? }
??????? return result;
??? }
??? //通過該方法,設置目標對象
??? public void setTarget(Object o)
??? {
??????? this.target = o;
??? }
}
該處理類實現InvocationHandler接口,實現該接口必須實現invoke(Object proxy, Method method, Object[] args)方法,程序調用代理的目標方法時,自動變成調用invoke方法。
該處理類并未與任何接口或類耦合,它完全是通用的,它的目標實例是Object類型,可以是任何的類型。
在invoke方法內,對目標對象的info方法進行簡單加強,在開始執行目標對象的方法之前,先打印開始事務,執行目標對象的方法之后,打印提交事務。
通過method對象的invoke方法,可以完成目標對象的方法調用,執行代碼如下:
result =method.invoke(target, args);
下面是代理工廠:
public class MyProxyFactory
{
??? /**
????? * 實例Service對象
???? * @param serviceName String
???? * @return Object
???? */
??? public static Object getProxy(Object object)
??? {
?????? //代理的處理類
??????? ProxyHandler handler = new ProxyHandler();
?????? //把該dog實例托付給代理操作
??????? handler.setTarget(object);
??????? //第一個參數是用來創建動態代理的ClassLoader對象,只要該對象能訪問Dog接口
??????? 即可
??????? //第二個參數是接口數組,正是代理該接口數組
??????? //第三個參數是代理包含的處理實例
??????? return Proxy.newProxyInstance(DogImpl.class.getClassLoader(),
??????????? object.getClass().getInterfaces(),handler);
??? }
}
代理工廠里有一行代碼:
Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),handler);
Proxy.newProxyInstance()方法根據接口數組動態創建代理類實例,接口數組通過object.getClass().getInterfaces()方法獲得,創建的代理類是JVM在內存中動態創建的,該類實現傳入接口數組的全部接口。
因此,Dynamic Proxy要求被代理的必須是接口的實現類,否則無法為其構造相應的動態類。因此,Spring對接口實現類采用Dynamic Proxy實現AOP,而對沒有實現任何接口的類,則通過CGLIB實現AOP代理。
下面是主程序:
public class TestDog
{
??? public static void main(String[] args)
??? {
??????? Dog dog = null;
??????? //創建Dog實例,該實例將作為被代理對象
??????? Dog targetObject = DogFactory.instance().getDog("gundog");
??????? //以目標對象創建代理
??????? Object proxy = MyProxyFactory.getProxy(targetObject);
??????? if (proxy instanceof Dog)
??????? {
??????????? dog = (Dog)proxy;
??????? }
??????? //測試代理的方法
??????? dog.info();
??????? dog.run();
??? }
}
代理實例會實現目標對象實現的全部接口。因此,代理實例也實現了Dog接口,程序運行結果如下:
[java] ======開始事務...
[java] 我是一只獵狗
[java] ======提交事務...
[java] 我奔跑迅速
代理實例加強了目標對象的方法——僅僅打印了兩行字符串。當然,此種加強沒有實際意義。試想一下,若程序中打印字符串的地方,換成真實的事務開始和事務提交,則代理實例的方法為目標對象的方法增加了事務性。
6.2.3 創建AOP代理
通過前面的介紹,AOP代理就是由AOP框架動態生成的一個對象,該對象可作為目標對象使用,AOP代理包含了目標對象的全部方法。但AOP代理中的方法與目標對象的方法存在差異:AOP方法在特定切面插入處理,在處理之間回調目標對象的方法。
AOP代理所包含的方法與目標對象所包含的方法的示意圖,如圖6.1所示。
Spring中AOP代理由Spring的IoC容器負責生成和管理,其依賴關系也由IoC容器負責管理。因此,AOP代理能夠引用容器中的其他Bean實例,這種引用由IoC容器的依賴注入提供。
Spring的AOP代理大都由ProxyFactoryBean工廠類產生,如圖6.1所示,產生一個AOP代理至少有兩個部分:目標對象和AOP框架所加入的處理。因此,配置ProxyFactoryBean時需要確定如下兩個屬性:
?? ● 代理的目標對象。
?? ● 處理(Advice)。
注意:關于Advice的更多知識,此處由于篇幅原因,無法深入討論。實際上,Spring也提供了很多種Advice的實現,如需要更深入了解Spring AOP,請讀者參考筆者所著的《Spring2.0寶典》。
所有代理工廠類的父類是org.springframework.aop.framework.ProxyConfig。因此,該類的屬性是所有代理工廠的共同屬性,這些屬性也是非常關鍵的,包括:
?? ● proxyTargetClass,確定是否代理目標類,如果需要代理目標是類,該屬性設為true,此時需要使用CGLIB生成代理;如果代理目標是接口,該屬性設為false,默認是false。
?? ● optimize,確定是否使用強優化來創建代理。該屬性僅對CGLIB代理有效;對JDK 動態代理無效。
?? ● frozen,確定是否禁止改變處理,默認是false。
?? ● exposeProxy,代理是否可以通過ThreadLocal訪問,如果exposeProxy屬性為true,則可通過AopContext.currentProxy()方法獲得代理。
?? ● aopProxyFactory,所使用的AopProxyFactory具體實現。該參數用來指定使用動態代理、CGLIB或其他代理策略。默認選擇動態代理或CGLIB。一般不需要指定該屬性,除非需要使用新的代理類型,才指定該屬性。
配置ProxyFactoryBean工廠bean時,還需要指定它的特定屬性,ProxyFactoryBean的特定屬性如下所示:
?? ● proxyInterfaces,接口名的字符串數組。如果沒有確定該參數,默認使用CGLIB代理。
?? ● interceptorNames,處理名的字符串數組。此處的次序很重要,排在前面的處理,優先被調用。此處的處理名,只能是當前工廠中處理的名稱,而不能使用bean引用。處理名字支持使用通配符(*)。
?? ● singleton,工廠是否返回單態代理。默認是true,無論 getObject()被調用多少次,將返回相同的代理實例。如果需要使用有狀態的處理——例如,有狀態的mixin,可改變默認設置,prototype處理。
posted @
2009-07-19 10:15 jadmin 閱讀(68) |
評論 (0) |
編輯 收藏
6.1 兩種后處理器
Spring 框架提供了很好的擴展性,除了可以與各種第三方框架良好整合外,其IoC容器也允許開發者進行擴展。這種擴展并不是通過實現BeanFactory或ApplicationContext的子類,而是通過兩個后處理器對IoC容器進行擴展。Spring提供了兩種常用的后處理器:
?? ● Bean后處理器,這種后處理器會對容器中特定的Bean進行定制,例如功能的??? 加強。
?? ● 容器后處理器,這種后處理器對IoC容器進行特定的后處理。
下面將介紹這兩種常用的后處理器以及兩種后處理器相關知識。
6.1.1 Bean后處理器
Bean后處理器是一種特殊的Bean,這種特殊的Bean并不對外提供服務,它無須id屬性,但它負責對容器中的其他Bean執行后處理,例如為容器中的目標Bean生成代理。這種Bean可稱為Bean后處理器,它在Bean實例創建成功后,對其進行進一步的加強???? 處理。
Bean后處理器必須實現BeanPostProcessor接口。
BeanPostProcessor接口包含兩個方法:
?? ● Object postProcessBeforeInitialization(Object bean, String name)throws BeansExce- ption,該方法的第一個參數是系統即將初始化的Bean實例,第二個參數是Bean實例的名字。
?? ● Object postProcessAfterInitialization(Object bean, String name)throws BeansExce- ption,該方法的第一個參數是系統剛完成初始化的Bean實例,第二個參數是Bean實例的名字。
實現該接口的Bean必須實現這兩個方法,這兩個方法會對容器的Bean進行后處理。兩個方法會在目標Bean初始化之前和初始化之后分別調用。這兩個方法用于對系統完成的默認初始化進行加強。
注意:Bean后處理器是對IoC容器一種極好的擴展,Bean后處理器可以對容器中的Bean進行后處理,這種后處理完全由開發者決定。
下面將定義一個簡單的Bean后處理器,該Bean后處理器將對容器中其他Bean進行后處理。Bean后處理器的代碼如下:
//自定義Bean后處理器,負責后處理容器中所有的Bean
public class MyBeanPostProcessor implements BeanPostProcessor
{
??? //在初始化bean之前,調用該方法
??? public Object postProcessBeforeInitialization(Object bean , String
??? beanName)throws BeansException
??? {
??????? //僅僅打印一行字符串
??????? System.out.println("系統正在準備對" + beanName + "進行初始化...");
??????? return bean;
??? }
??? //在初始化bean之后,調用該方法
??? public Object postProcessAfterInitialization(Object bean , String
??? beanName)throws BeansException
??? {
??????? System.out.println("系統已經完成對" + beanName + "的初始化");
??????? //如果系統剛完成初始化的bean是Chinese
??????? if (bean instanceof Chinese)
??????? {
??????????? //為Chinese實例設置name屬性
??????????? Chinese c = (Chinese)bean;
??????????? c.setName("wawa");
????? ?? }
??????? return bean;
??? }
}
下面是Chinese的源代碼,該類實現了InitializingBean接口,還額外提供了一個初始化方法,這兩個方法都由Spring容器控制回調。
public class Chinese implements Person,InitializingBean
{
??? private Axe axe;
??? private String name;
??? public Chinese()
??? {
??????? System.out.println("Spring實例化主調bean:Chinese實例...");
??? }
??? public void setAxe(Axe axe)
??? {
??????? System.out.println("Spring執行依賴關系注入...");
??????? 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("正在執行初始化方法?? init...");
??? }
?? public void afterPropertiesSet() throws Exception
??? {
?????? System.out.println("正在執行初始化方法 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后處理器,可以沒有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>
本應用的chinese具有兩個初始化方法:
?? ● init-method指定初始化方法。
?? ● 實現InitializingBean接口,提供了afterPropertiesSet初始化方法。
MyBeanPostProcessor類實現了BeanPostProcessor接口,并實現了該接口的兩個方法,這兩個方法分別在初始化方法調用之前和之后得到回調。
注意:上面的配置文件配置Bean后處理器時,依然為Bean處理器指定了id屬性,指定id屬性是為了方便程序通過該id屬性訪問Bean后處理器。大部分時候,程序無須手動訪問該Bean后處理器,因此無須為其指定id屬性。
主程序如下:
public class BeanTest
{
??? public static void main(String[] args)throws Exception
??? {
??????? //CLASSPATH路徑下的bean.xml文件創建Resource對象
??????? ClassPathResource isr = new ClassPathResource("bean.xml");
??????? //以Resource對象作為參數,創建BeanFactory的實例
??????? XmlBeanFactory factory = new XmlBeanFactory(isr);
??????? //獲取Bean后處理器實例
??????? MyBeanPostProcessor beanProcessor =
??????????? (MyBeanPostProcessor)factory.getBean("beanPostProcessor");
??????? //注冊BeanPostProcessor實例
??????? factory.addBeanPostProcessor(beanProcessor);
??????? System.out.println("程序已經實例化BeanFactory...");
??????? Person p = (Person)factory.getBean("chinese");
??????? System.out.println("程序中已經完成了chinese bean的實例化...");
??????? p.useAxe();
??? }
}
如果使用BeanFactory作為Spring容器,必須手動注冊Bean后處理器,因此在程序中先獲取Bean后處理器實例,然后手動注冊——這就是在配置文件中指定Bean后處理器id屬性的原因。通過BeanFactory的addBeanPostProcessor可以注冊BeanPostProcessor實例。程序執行結果如下:
[java] 程序已經實例化BeanFactory...
[java] Spring實例化主調bean:Chinese實例...
[java] Spring實例化依賴bean:SteelAxe實例...
[java] 系統正在準備對steelAxe進行初始化...
[java] 系統已經完成對steelAxe的初始化
[java] Spring執行依賴關系注入...
[java] 系統正在準備對chinese進行初始化...
[java] 正在執行初始化方法 afterPropertiesSet...
[java] 正在執行初始化方法?? init...
[java] 系統已經完成對chinese的初始化
[java] 程序中已經完成了chinese bean的實例化...
[java] wawa鋼斧砍柴真快
在配置文件中配置chinese實例時,并未指定name屬性值。但程序執行時,name屬性有了值,這就是Bean后處理器完成的,在Bean后處理器中判斷Bean是否是Chinese實例,然后設置它的name屬性。
容器中一旦注冊了Bean后處理器,Bean后處理器會自動啟動,在容器中每個Bean創建時自動工作,完成加入Bean后處理器需要完成的工作。
實現BeanPostProcessor接口的Bean后處理器可對Bean進行任何操作,包括完全忽略這個回調。BeanPostProcessor通常用來檢查標記接口或將Bean包裝成一個Proxy的事情。Spring的很多工具類,就是通過Bean后處理器完成的。
從主程序中看到,采用BeanFactory作為Spring容器時,必須手動注冊BeanPost- Processor。而對于ApplicationContext,則無須手動注冊。ApplicationContext可自動檢測到容器中的Bean后處理器,自動注冊。Bean后處理器會在Bean實例創建時,自動啟動。即主程序采用如下代碼,效果完全一樣:
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("程序中已經完成了chinese bean的實例化...");
??????? p.useAxe();
??? }
}
使用ApplicationContext作為容器,無須手動注冊BeanPostProcessor。因此,如果需要使用Bean后處理器,Spring容器建議使用ApplicationContext,而不是BeanFactory。
6.1.2 Bean后處理器的用處
上一節介紹了一個簡單的Bean后處理器,上面的Bean后處理器負責對容器中的Chinese Bean進行后處理,不管Chinese Bean如何初始化,總是將Chinese Bean的name屬性設置為wawa。這種后處理看起來作用并不是特別大。
實際上,Bean后處理器完成的工作更加實際,例如生成Proxy。Spring框架本身提供了大量的Bean后處理器,這些后處理器負責對容器中的Bean進行后處理。
下面是Spring提供的兩個常用的后處理器:
?? ● BeanNameAutoProxyCreator,根據Bean實例的name屬性,創建Bean實例的代理。
?? ● DefaultAdvisorAutoProxyCreator,根據提供的Advisor,對容器中所有的Bean實例創建代理。
上面提供的兩個Bean后處理器,都用于根據容器中配置的攔截器創建目標Bean代理,目標代理就在目標Bean的基礎上修改得到。
注意:如果需要對容器中某一批Bean進行特定的處理,可以考慮使用Bean后處理器。
6.1.3 容器后處理器
除了上面提供的Bean后處理器外,Spring還提供了一種容器后處理器。Bean后處理器負責后處理容器生成的所有Bean,而容器后處理器則負責后處理容器本身。
容器后處理器必須實現BeanFactoryPostProcessor接口。實現該接口必須實現如下一個方法:
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
實現該方法的方法體就是對Spring容器進行的處理,這種處理可以對Spring容器進行任意的擴展,當然也可以對Spring容器不進行任何處理。
類似于BeanPostProcessor,ApplicationContext可自動檢測到容器中的容器后處理器,并且自動注冊容器后處理器。但若使用BeanFactory作為Spring容器,則必須手動注冊后處理器。
下面定義了一個容器后處理器,這個容器后處理器實現BeanFactoryPostProcessor接口,但并未對Spring容器進行任何處理,只是打印出一行簡單的信息。該容器后處理器的代碼如下:
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor
{
??? //容器后處理器對容器進行的處理在該方法中實現
??? public void postProcessBeanFactory(ConfigurableListableBeanFactory
??? beanFactory)
??????? throws BeansException
??? {
??????? System.out.println("程序對Spring所做的BeanFactory的初始化沒有意
??????? 見...");
??? }
}
將該Bean作為普通Bean部署在容器中,然后使用ApplicationContext作為容器,容器會自動調用BeanFactoryPostProcessor處理Spring容器。程序執行效果如下:
[java] 程序對Spring所做的BeanFactory的初始化沒有意見...
實現BeanFactoryPostProcessor接口的Bean后處理器不僅可對BeanFactory執行后處理,也可以對ApplicationContext容器執行后處理。容器后處理器還可用來注冊額外的屬性編輯器。
注意:Spring沒有提供ApplicationContextPostProcessor。也就是說,對于Application- Context容器,一樣使用BeanFactoryPostProcessor作為容器后處理器。
Spring已提供如下兩個常用的容器后處理器,包括:
?? ● PropertyResourceConfigurer,屬性占位符配置器。
?? ● PropertyPlaceHolderConfigurer,另一種屬性占位符配置器。
下面將詳細介紹這兩種常用的容器后處理器。
6.1.4 屬性占位符配置器
Spring提供了PropertyPlaceholderConfigurer,它是一個容器后處理器,負責讀取Java屬性文件里的屬性值,并將這些屬性值設置到Spring容器定義中。
通過使用PropertyPlaceholderConfigurer后處理器,可以將Spring配置文件中的部分設置放在屬性文件中設置。這種配置方式當然有其優勢:可以將部分相似的配置(如數據庫的urls、用戶名和密碼)放在特定的屬性文件中,如果只需要修改這部分配置,則無須修改Spring配置文件,修改屬性文件即可。
下面的配置文件配置了PropertyPlaceholderConfigurer后處理器,在配置數據源Bean時,使用了屬性文件中的屬性值。配置文件的代碼如下:
<?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">
??? <!-- 配置一個容器后處理器Bean -->
??? <bean id="propertyConfigurer"
??????? class="org.springframework.beans.factory.config.
??????? PropertyPlaceholderConfigurer">
??????? <!-- locations屬性指定屬性文件的位置 -->
??????? <property name="locations">
??????????? <list>
??????????????? <value>dbconn.properties</value>
??????????????? <!-- 如果有多個屬性文件,依次在下面列出來 -->
??????????? </list>
??????? </property>
??? </bean>
??? <!-- 定義數據源Bean,使用C3P0數據源實現 -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0.
??? ComboPooledDataSource" destroy-method="close">
??????? <!-- 指定連接數據庫的驅動 -->
??????? <property name="driverClass" value="${jdbc.driverClassName}"/>
??????? <!-- 指定連接數據庫的URL -->
??????? <property name="jdbcUrl" value="${jdbc.url}"/>
??????? <!-- 指定連接數據庫的用戶名 -->
??????? <property name="user" value="${jdbc.username}"/>
??????? <!-- 指定連接數據庫的密碼 -->
??????? <property name="password" value="${jdbc.password}"/>
??? </bean>
</beans>
在上面的配置文件中,配置driverClass和jdbcUrl等信息時,并未直接設置這些屬性的屬性值,而是設置了${jdbc.driverClassName}和${jdbc.url}屬性值。這表明Spring容器將從propertyConfigurer指定屬性文件中搜索這些key對應的value,并為該Bean的屬性值設置這些value值。
如前所述,ApplicationContext會自動檢測部署在容器的容器后處理器,無須額外的注冊,容器自動注冊。因此,只需提供如下Java Properties文件:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/j2ee
jdbc.username=root
jdbc.password=32147
通過這種方法,可從主XML配置文件中分離出部分配置信息。如果僅需要修改數據庫連接屬性,則無須修改主XML配置文件,只需要修改屬性文件即可。采用屬性占位符的配置方式,可以支持使用多個屬性文件。通過這種方式,可將配置文件分割成多個屬性文件,從而降低修改配置的風險。
注意:對于數據庫連接等信息集中的配置,可以將其配置在Java屬性文件中,但不要過多地將Spring配置信息抽離到Java屬性文件中,否則可能會降低Spring配置文件的可讀性。
6.1.5 另一種屬性占位符配置器(PropertyOverrideConfigurer)
PropertyOverrideConfigurer是Spring提供的另一個容器后處理器,這個后處理器的額作用與上面介紹的容器后處理器作用大致相同。但也存在些許差別:PropertyOverride- Configurer使用的屬性文件用于覆蓋XML配置文件中的定義。即PropertyOverride- Configurer允許XML配置文件中有默認的配置信息。
如果PropertyOverrideConfigurer的屬性文件有對應配置信息,XML文件中的配置信息被覆蓋;否則,直接使用XML文件中的配置信息。使用PropertyOverrideConfigurer的屬性文件,應是如下的格式:
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">
??? <!-- 配置一個屬性占位符Bean。ApplictionContext能自動識別
??? PropertyPlaceholderConfigurer Bean -->
??? <bean id="propertyOverrider"
??????? class="org.springframework.beans.factory.config.
??????? PropertyOverrideConfigurer">
??????? <property name="locations">
??????????? <list>
??? ??????????? <value>dbconn.properties</value>
??????????????? <!-- 如果有多個屬性文件,依次在下面列出來 -->
??????????? </list>
??????? </property>
??? </bean>
??? <!-- 定義數據源Bean,使用C3P0數據源實現 -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0.
??? ComboPooledDataSource" destroy-method="close">
??????? <!-- 指定連接數據庫的驅動 -->
??????? <property name="driverClass" value="dd"/>
??????? <!-- 指定連接數據庫的URL -->
??????? <property name="jdbcUrl" value="xx"/>
??????? <!-- 指定連接數據庫的用戶名 -->
??????? <property name="user" value="dd"/>
??????? <!-- 指定連接數據庫的密碼 -->
??????? <property name="password" value="xx"/>
??? </bean>
</beans>
上面的配置文件中,指定數據源Bean的各種屬性值時,只是隨意指定了幾個屬性值,很明顯通過這幾個屬性值無法連接到數據庫服務。
但因為Spring容器中部署了一個PropertyOverrideConfigurer的容器后處理器,而且Spring容器使用ApplicationContext作為容器,它會自動檢測容器中的容器后處理器,無須額外的注冊,容器自動注冊該后處理器。
PropertyOverrideConfigurer后處理器讀取dbconn.properties文件中的屬性,用于覆蓋目標Bean的屬性。因此,如果屬性文件中有dataSource Bean屬性的設置,則配置文件中指定的屬性值將沒有任何作用。
dbconn.properties屬性文件如下:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://wonder:3306/j2ee
dataSource.username=root
dataSource.password=32147
注意屬性文件的格式必須是:
beanName.property=value
也就是說,dataSource必須是容器中真實存在的bean名,否則程序將出錯。
注意:程序無法知道BeanFactory定義是否被覆蓋。僅僅通過察看XML配置文件,無法知道配置文件的配置信息是否被覆蓋。如有多個PorpertyOverrideConfigurer對同一Bean屬性定義了覆蓋,最后一個覆蓋獲勝。
posted @
2009-07-19 10:12 jadmin 閱讀(121) |
評論 (0) |
編輯 收藏