hk2000c技術專欄

          技術源于哲學,哲學來源于生活 關心生活,關注健康,關心他人

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            111 隨筆 :: 1 文章 :: 28 評論 :: 0 Trackbacks

          #

          CAS (Central Authentication Service) 是 Yale 大學的 ITS 開發的一套 JAVA 實現的開源

          的 SSO(single sign-on) 的服務(主要是2.0,到3.0為ja-sig)。

          關鍵字

          TGC(ticket-granting cookie)--------- 受權的票據證明

          KDC( Key Distribution Center ) ---------- 密鑰發放中心

          Service ticket(ST) --------- 服務票據, 由 KDC 的 TGS 發放。 任何一臺 Workstation 都需要擁有一張有效的 Service Ticket 才能訪問域內部的應用 (Applications) 。 如果能正確接收 Service Ticket ,說明在 CASClient-CASServer 之間的信任關系已經被正確建立起來 , 通常為一張數字加密的證書

          Ticket Granting tieckt(TGT) --------- 票據授權票據,由 KDC 的 AS 發放。即獲取這樣一張票據后,以后申請各種其他服務票據 (ST) 便不必再向 KDC 提交身份認證信息 ( 準確術語是 Credentials) 。

          authentication service (AS) --------- 認證用服務,索取 Crendential ,發放 TGT

          ticket-granting service (TGS) --------- 票據授權服務,索取 TGT ,發放 ST

          CAS 單點服務器的認證過程,所有應用服務器收到應用請求后,檢查 ST 和 TGT ,如果沒有或不對,轉到 CAS 認證服務器登陸頁面,通過安全認證后得到 ST 和 TGT 再重定向到相關應用服務器,在會話生命周期之內如果再定向到別的應用,將出示

          ST 和 TGT 進行認證 , 注意 , 取得 TGT 的過程是通過 SSL 安全協議的 ( 換句話說就是如果不用 ssl 協議 , 每訪問一個應用服務,就得重新到認證服務中心認證一次 ) ,關于 SSL 的相關描述可以查看附錄 .

          白話描述 :

          單點登陸 , 無非就是提供給用戶一次登陸 , 多個系統共享用戶信息的操作 .

          這個是怎么操作的呢 ? 有簡單的方法 , 當用戶訪問其他系統的時候 , 寫個 URL 帶上用戶的 ID 和 PASS 提交到相應的系統就可以了 . 這也是一種方法

          那 CAS 是怎么操作的呢 ? 或則是 KRB(Kerberos 是一個加密認證協議,允許網絡用戶不使用明文密碼訪問服務,一個普通
          的協議實現包括 LOGIN 服務存在偽造欺騙對 Key Distribution Center 的響應 。

          ) 怎么操作的呢 ?

          他并不是很復雜 , 他先是建立一個 專門認證用戶的 服務 (SERVER) 這個服務只做一件事 , 負責驗證用戶的 ID 和 PASS 是否是正確 , 在正確的情況提供用戶一個名為 TGT 的票據 ,

          相當你要去游樂場玩 , 首先你要在門口檢查你的身份 ( 即 CHECK 你的 ID 和 PASS), 如果你通過驗證 , 游樂場的門衛 (AS) 即提供給你一張門卡 (TGT).

          這張卡片的用處就是告訴 游樂場的各個場所 , 你是通過正門進來 , 而不是后門偷爬進來的 , 并且也是獲取進入場所一把鑰匙 .

          好的 , 現在你有張卡 , 但是這對你來不重要 , 因為你來游樂場不是為了拿這張卡的 , 好的 , 我們向你的目的出發 , 恩 , 你來到一個摩天樓 , 你想進入玩玩 ,

          這時摩天輪的服務員 (client) 攔下你 , 向你要求摩天輪的 (ST) 票據 , 你說你只有一個門卡 (TGT), 好的 , 那你只要把 TGT 放在一旁的票據授權機 (TGS) 上刷一下 ,

          票據授權機 (TGS) 就根據你現在所在的摩天輪 , 給你一張摩天輪的票據 (ST), 哈 , 你有摩天輪的票據 , 現在你可以暢通無阻的進入摩天輪里游玩了 .

          當然如果你玩完摩天輪后 , 想去游樂園的咖啡廳休息下 , 那你一樣只要帶著那張門卡 (TGT). 到相應的咖啡廳的票據授權機 (TGS) 刷一下 , 得到咖啡廳的票據 (ST) 就可以進入咖啡廳

          當你離開游樂場后 , 想用這張 TGT 去刷打的回家的費用 , 呵呵 , 對不起 , 你的 TGT 已經過期了 , 在你離開游樂場那刻開始 , 你的 TGT 就已經銷毀了 ~

          Yale CAS Server 的配置過程

          CAS (Central Authentication Service) 是 Yale 大學的 ITS 開發的一套 JAVA 實現的開源

          的 SSO(single sign-on) 的服務。該服務是以一個 java web app(eg:cas.war) 來進行服務的,

          使用時需要將 cas.war 發布到一個 servlet2.3 兼容的服務器上,并且服務器需要支持 SSL ,

          在需要使用該服務的其他服務器(客戶),只要進行簡單的配置就可以實現 SSO 了。

          CAS 的客戶端可以有很多種,因為驗證的結果是以 XML 的格式返回的, CAS 的客戶端已

          打包進去的有 java,perl,python,asp,apache module 等好幾種客戶端示例,你還可以根據

          需要實現一個自己的客戶端,非常簡單 !~

          下面我們以 tomcat 5.0 作為 CAS Server(server1) ,另外一臺 tomcat5.0 為 client(client1)

          為例進行說明。

          1. 下載 cas-server 和 cas-client( 可選,建議使用)

          http://www.ja-sig.org/downloads/cas/cas-server-3.0.5.zip

          http://www.ja-sig.org/downloads/cas-clients/cas-client-java-2.1.1.zip

          2. 將 cas-server-3.0.5.zip 解壓,并將 lib/cas.war 拷貝到 server1 的 webapps 下

          3. 產生 SERVER 的證書

          PS: 參數與各系統本身一致

          %JAVA_HOME%\bin\keytool -delete -alias tomcat -keypass changeit

          %JAVA_HOME%\bin\keytool -genkey -alias tomcat -keypass changeit -keyalg RSA

          %JAVA_HOME%\bin\keytool -export -alias tomcat -keypass changeit -file %FILE_NAME%

          %JAVA_HOME%\bin\keytool -import -file server.crt -keypass changeit -keystore %JAVA_HOME%/jre/lib/security/cacerts

          %JAVA_HOME%\bin\keytool -import -file server.crt -keystore %JAVA_HOME%\jre\lib\security\cacerts

          4. 在 server1 配置 tomcat 使用 HTTPS

          $CATALINA_HOME/conf/server.xml 里

          <Connector className="org.apache.coyote.tomcat5.CoyoteConnector"

          port="8443" minProcessors="5" maxProcessors="75"

          enableLookups="true" disableUploadTimeout="true"

          acceptCount="100" debug="0" scheme="https"

          secure="true">

          <Factory className="org.apache.coyote.tomcat5.CoyoteServerSocketFactory"

          keystoreFile="/path/to/your/keystore-file"

          keystorePass="your-password" clientAuth="false" protocol="TLS" />

          </Connector>

          5. 在要使用 CAS 的客戶端 client1 里設置(以 servlets-examples 這個 APP 為例),我們使用

          ServletFilter(CAS client 里提供的 ) 來實現 SSO 的檢查。

          修改 servlets-examples/WEB-INF/web.xml

          <filter>

          <filter-name>CASFilter</filter-name>

          <filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>

          <init-param>

          <param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>

          <param-value>https://your.cas.server.name(eg:server1):port/cas/login</param-value>

          </init-param>

          <init-param>

          <param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>

          <param-value>https://your.cas.server.name(eg:server1):port/cas/proxyValidate</param-value>

          </init-param>

          <init-param>

          <param-name>edu.yale.its.tp.cas.client.filter.serviceUrl</param-name>

          <param-value>your.client.server.ip(eg:127.0.0.1):port</param-value>

          </init-param>

          </filter>

          <filter-mapping>

          <filter-name>CASFilter</filter-name>

          <url-pattern>/servlet/*</url-pattern>

          </filter-mapping>

          PS: 在 client 端配置 filter 時 , 需要將 CAS 的 filter 放在 web.xml 最上端 ,. 如果在你的 web.xml 有類似 encodingFilter 的 filter 也需要將這個 filter 放在 CAS filter 下面 , 否則你會發現每次訪問時都需要你進行驗證 .

          6. 將 cas-client-java-2.1.1.zip 解壓,把 java/lib/casclient.jar 拷貝到 client1 服務器上的

          webapps/servlets-examples/WEB-INF/lib 目錄下(如果沒有就建一個)

          7. 導出 SERVER 的證書,用來給所有需要用到的客戶端導入

          keytool -export -file server.crt -alias my-alias-name -keystore keystore-file

          8. 在客戶端的 JVM 里導入信任的 SERVER 的證書 ( 根據情況有可能需要管理員權限 )

          keytool -import -keystore $JAVA_HOME/jre/lib/security/cacerts -file server.crt -alias my-alias-name

          9.test & done.

          把 server1 和 client1 分別起來,檢查啟動的 LOG 是否正常,如果一切 OK ,就訪問

          http://client1:8080/servlets-examples/servlet/HelloWorldExample

          系統會自動跳轉到一個驗證頁面,隨便輸入一個相同的賬號 , 密碼,嚴正通過之后就會訪問

          到真正的 HelloWorldExample 這個 servlet 了

          實現自已的認證代碼 (java 代碼和相關注釋 , 需要 cas-server-3.0.5.jar 包 )


          package com.mcm.sso;

           

          import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;

          import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;

          import org.springframework.util.StringUtils;

           

          public class MyUsernamePasswordAuthenticationHandler extends

          AbstractUsernamePasswordAuthenticationHandler {

           

          public boolean authenticateUsernamePasswordInternal(

          final UsernamePasswordCredentials credentials) {

          final String username = credentials.getUsername();

          final String password = credentials.getPassword();

           

          // 此處實現你的登陸驗證代碼

          if (StringUtils.hasText(username) && StringUtils.hasText(password) ) {

          getLog().debug(

          " User [ " + username + " ] was successfully authenticated with ucix. " );

          return true ;

          }

           

          getLog().debug( " User [ " + username + " ] failed authentication " );

           

          return false ;

          }

           

          protected void afterPropertiesSetInternal() throws Exception {

          super .afterPropertiesSetInternal();

          }

          }

           

          然后將這個類配置到 deployerConfigContext.xml 文件里 , 替代 <bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />

          可能要用到數據庫連接之類的配置,具體可參照 spring framework 相關文檔

          在 client 端取到登陸相關信息及登出系統

          1. 取得用用戶 ID

          以下兩種方式都可以

          session.getAttribute(CASFilter.CAS_FILTER_USER);

          session.getAttribute("edu.yale.its.tp.cas.client.filter.user");

          也可以直接取得認證 java 對象

          session.getAttribute(CASFilter.CAS_FILTER_RECEIPT);

          session.getAttribute("edu.yale.its.tp.cas.client.filter.receipt");

          JSP2.0 標準寫法

          <c:out value="${sessionScope['edu.yale.its.tp.cas.client.filter.user']}"/>

          在 jsp 中使用 CAS Tag Library 標簽

          除實現以上功能完還可以實現登出之類的相關功能,具體參照 cas 官方文檔

          http://www.ja-sig.org/products/cas/client/jsp/index.html

          附錄

          1 . SSL(Server Socket Layer) 簡介

          在網絡上信息在源 - 宿的傳遞過程中會經過其它的計算機。一般情況下,中間的計算機不會監聽路過的信息。但在使用網上銀行或者進行信用卡交易的時候有可能被監視,從而導致個人隱私的泄露。由于 Internet 和 Intranet 體系結構的原因,總有某些人能夠讀取并替換用戶發出的信息。隨著網上支付的不斷發展,人們對信息安全的要求越來越高。因此 Netscape 公司提出了 SSL 協議,旨在達到在開放網絡 (Internet) 上安全保密地傳輸信息的目的,這種協議在 WEB 上獲得了廣泛的應用。 之后 IETF(ietf.org) 對 SSL 作了標準化,即 RFC2246 ,并將其稱為 TLS ( Transport Layer Security ),從技術上講, TLS1.0 與 SSL3.0 的差別非常微小。

          2 . SSL 工作原理

          SSL 協議使用不對稱加密技術實現會話雙方之間信息的安全傳遞。可以實現信息傳遞的保密性、完整性,并且會話雙方能鑒別對方身份。不同于常用的 http 協議,我們在與網站建立 SSL 安全連接時使用 https 協議,即采用 https://ip:port/ 的方式來訪問。當我們與一個網站建立 https 連接時,我們的瀏覽器與 Web Server 之間要經過一個握手的過程來完成身份鑒定與密鑰交換,從而建立安全連接。具體過程如下:

          用戶瀏覽器將其 SSL 版本號、加密設置參數、與 session 有關的數據以及其它一些必要信息發送到服務器。

          服務器將其 SSL 版本號、加密設置參數、與 session 有關的數據以及其它一些必要信息發送給瀏覽器,同時發給瀏覽器的還有服務器的證書。如果配置服務器的 SSL 需要驗證用戶身份,還要發出請求要求瀏覽器提供用戶證書。

          客戶端檢查服務器證書,如果檢查失敗,提示不能建立 SSL 連接。如果成功,那么繼續。客戶端瀏覽器為本次會話生成 pre-master secret ,并將其用服務器公鑰加密后發送給服務器。如果服務器要求鑒別客戶身份,客戶端還要再對另外一些數據簽名后并將其與客戶端證書一起發送給服務器。

          如果服務器要求鑒別客戶身份,則檢查簽署客戶證書的 CA 是否可信。如果不在信任列表中,結束本次會話。如果檢查通過,服務器用自己的私鑰解密收到的 pre-master secret ,并用它通過某些算法生成本次會話的 master secret 。

          客戶端與服務器均使用此 master secret 生成本次會話的會話密鑰 ( 對稱密鑰 ) 。在雙方 SSL 握手結束后傳遞任何消息均使用此會話密鑰。這樣做的主要原因是對稱加密比非對稱加密的運算量低一個數量級以上,能夠顯著提高雙方會話時的運算速度。

          客戶端通知服務器此后發送的消息都使用這個會話密鑰進行加密。并通知服務器客戶端已經完成本次 SSL 握手。

          服務器通知客戶端此后發送的消息都使用這個會話密鑰進行加密。并通知客戶端服務器已經完成本次 SSL 握手。

          本次握手過程結束,會話已經建立。雙方使用同一個會話密鑰分別對發送以及接受的信息進行加、解密。

          posted @ 2007-11-16 15:50 hk2000c 閱讀(1114) | 評論 (0)編輯 收藏

          BO設計定稿

          等過些時候重新審視自己的設計再說


          posted @ 2007-11-15 19:26 hk2000c 閱讀(210) | 評論 (0)編輯 收藏

          org.springframework.web.bind.ServletRequestDataBinder@27ca12


          DataBinder: applyPropertyValues(MutablePropertyValues mpvs)


          org.springframework.beans.BeanWrapperImpl: wrapping object [com.unitedbiz.model.User@996f0e]
          AbstractPropertyAccessor::setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)

           

          org.springframework.beans.BeanWrapperImpl: wrapping object [com.unitedbiz.model.User@996f0e]::setPropertyValue(PropertyValue pv)

           

          posted @ 2007-11-12 05:22 hk2000c 閱讀(283) | 評論 (0)編輯 收藏

          filter 位置不能顛倒

          一般位置順序為

          encoding

          hibernate

          localeFilter

          securityFilter

          sitemesh

          posted @ 2007-11-08 19:29 hk2000c 閱讀(223) | 評論 (0)編輯 收藏

          .在validation.xml中進行相關的驗證配置如:
          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN" "validator_1_0.dtd" >
          <form-validation>
          <formset>
          <form name="loginForm">//struts-config.xml中formBean的名字
          <field property="username" depends="required,maxlength,minlength">
          //property相關驗證字段的名稱,depends所對應的驗證器
          <arg0 key="用戶名" resource="false"/>
          //當resource為TRUE時,表示使用來自resource Bundle中的消息,反之指定key中消息
          <arg1 name="maxlength" resource="false" key="${var:maxlength}"/>
          <var>
          <var-name>maxlength</var-name>
          <var-value>8</var-value>
          </var>
          <arg2 name="minlength" resource="false" key="${var:minlength}"/>
          <var>
          <var-name>minlength</var-name>
          <var-value>2</var-value>
          </var>

          </field>
          </form>
          </formset>

          所注意的問題:1、如<arg0 key="用戶名" resource="false"/>在arg0中如果不設置驗證器,那么就被所有的驗證器通用
          2、如<arg1 name="maxlength" resource="false" key="${var:maxlength}"/>中的arg1要與錯誤信息中所對應的該驗證器的參數一至(errors.maxlength={0} can not be greater than {1} characters.)
          如果(errors.maxlength={0} can not be greater than {2} characters.)的話,那么就必須將2改為1

          7.jsp中的前臺驗證:
          <htm:form arction =”” onsubmit=” return validateLoginForm(this) ”>
          <html:javascript formName="loginForm"/>
          //對應formBean的名字
          posted @ 2007-11-08 01:10 hk2000c 閱讀(1750) | 評論 (0)編輯 收藏


          關鍵樣式:
          table-layout:fixed 固定布局的算法,則表格被呈遞的默認寬度為 100% (For IE,Mozilla)
          text-overflow:ellipsis 當對象內文本溢出時顯示省略標記(...) (For IE)
          overflow:hidden 不顯示超過對象尺寸的內容 (For IE,Mozilla)
          white-space: nowrap 強制在同一行內顯示所有文本,直到文本結束或者遭遇 br 對象 (For IE,Mozilla)




           .ctl{
             table-layout:fixed
           }
            .ctl td{text-overflow:ellipsis;overflow:hidden;white-space: nowrap;padding:2px}

           <table cellSpacing="0" cellpadding="1" width="100" class="ctl" border=1>
           

          即可使用

          posted @ 2007-11-07 15:33 hk2000c 閱讀(863) | 評論 (0)編輯 收藏

          OSCache 避免死鎖

          在正常更新 cache 后, 不要調用 

                         cache.cancelUpdate(url);

          方法


          否則會引起系統死鎖



          posted @ 2007-11-07 13:09 hk2000c 閱讀(359) | 評論 (0)編輯 收藏

          Spring聲明式事務讓我們從復雜的事務處理中得到解脫。使得我們再也無需要去處理獲得連接、關閉連接、事務提交和回滾等這些操作。再也無需要我們在與事務相關的方法中處理大量的try…catch…finally代碼。
          我們在使用Spring聲明式事務時,有一個非常重要的概念就是事務屬性。事務屬性通常由事務的傳播行為,事務的隔離級別,事務的超時值和事務只讀標志組成。我們在進行事務劃分時,需要進行事務定義,也就是配置事務的屬性。
          Spring在TransactionDefinition接口中定義這些屬性,以供PlatfromTransactionManager使用, PlatfromTransactionManager是spring事務管理的核心接口。

          代碼
          1. TransactionDefinition   
          2. public interface TransactionDefinition {   
          3.     int getPropagationBehavior();   
          4.     int getIsolationLevel();   
          5.     int getTimeout();   
          6.     boolean isReadOnly();   
          7. }  

           

          getTimeout()方法,它返回事務必須在多少秒內完成。
          isReadOnly(),事務是否只讀,事務管理器能夠根據這個返回值進行優化,確保事務是只讀的。
          getIsolationLevel()方法返回事務的隔離級別,事務管理器根據它來控制另外一個事務可以看到本事務內的哪些數據。

          在TransactionDefinition接口中定義了五個不同的事務隔離級別
          ISOLATION_DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別.另外四個與JDBC的隔離級別相對應
          ISOLATION_READ_UNCOMMITTED 這是事務最低的隔離級別,它充許別外一個事務可以看到這個事務未提交的數據。這種隔離級別會產生臟讀,不可重復讀和幻像讀。
          例如:
          Mary的原工資為1000,財務人員將Mary的工資改為了8000,但未提交事務

          代碼
          1. Connection con1 = getConnection();   
          2. con.setAutoCommit(false);   
          3. update employee set salary = 8000 where empId ="Mary";  

          與此同時,Mary正在讀取自己的工資
          代碼
          1. Connection con2 = getConnection();   
          2. select  salary from employee where empId ="Mary";   
          3. con2.commit();  

           

          Mary發現自己的工資變為了8000,歡天喜地!
          而財務發現操作有誤,而回滾了事務,Mary的工資又變為了1000

          代碼
          1. //con1   
          2.   con1.rollback();  

          像這樣,Mary記取的工資數8000是一個臟數據。

           

          ISOLATION_READ_COMMITTED 保證一個事務修改的數據提交后才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據。這種事務隔離級別可以避免臟讀出現,但是可能會出現不可重復讀和幻像讀。

          ISOLATION_REPEATABLE_READ 這種事務隔離級別可以防止臟讀,不可重復讀。但是可能出現幻像讀。它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了避免下面的情況產生(不可重復讀)。

          在事務1中,Mary 讀取了自己的工資為1000,操作并沒有完成

          代碼
          1. con1 = getConnection();   
          2. select salary from employee empId ="Mary";  

           

          在事務2中,這時財務人員修改了Mary的工資為2000,并提交了事務.

          代碼
          1. con2 = getConnection();   
          2. update employee set salary = 2000;   
          3. con2.commit();  

           

          在事務1中,Mary 再次讀取自己的工資時,工資變為了2000

          代碼
          1. //con1   
          2. select salary from employee empId ="Mary";  

           

          在一個事務中前后兩次讀取的結果并不致,導致了不可重復讀。
          使用ISOLATION_REPEATABLE_READ可以避免這種情況發生。

          ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。除了防止臟讀,不可重復讀外,還避免了幻像讀。

          目前工資為1000的員工有10人。
          事務1,讀取所有工資為1000的員工。

          代碼
          1. con1 = getConnection();   
          2. Select * from employee where salary =1000;  
          共讀取10條記錄

           

          這時另一個事務向employee表插入了一條員工記錄,工資也為1000

          代碼
          1. con2 = getConnection();   
          2. Insert into employee(empId,salary) values("Lili",1000);   
          3. con2.commit();  

           

          事務1再次讀取所有工資為1000的員工

          代碼
          1. //con1   
          2. select * from employee where salary =1000;  

           

          共讀取到了11條記錄,這就產生了幻像讀。
          ISOLATION_SERIALIZABLE能避免這樣的情況發生。但是這樣也耗費了最大的資源。

          getPropagationBehavior()返回事務的傳播行為,由是否有一個活動的事務來決定一個事務調用。

          在TransactionDefinition接口中定義了七個事務傳播行為

          PROPAGATION_REQUIRED 如果存在一個事務,則支持當前事務。如果沒有事務則開啟一個新的事務。

          代碼
          1. //事務屬性 PROPAGATION_REQUIRED   
          2. methodA{   
          3. ……   
          4. methodB();   
          5. ……   
          6. }   
          7.   
          8. //事務屬性 PROPAGATION_REQUIRED   
          9. methodB{   
          10.    ……   
          11. }  

          使用spring聲明式事務,spring使用AOP來支持聲明式事務,會根據事務屬性,自動在方法調用之前決定是否開啟一個事務,并在方法執行之后決定事務提交或回滾事務。

           

          單獨調用methodB方法

          代碼
          1. main{   
          2.   metodB();   
          3. }  

          相當于
          代碼
          1. Main{   
          2. Connection con=null;   
          3.   
          4.    rry{   
          5.       con = getConnection();   
          6.       con.setAutoCommit(false);   
          7. //方法調用   
          8. methodB();   
          9. //提交事務   
          10. con.commit();   
          11. }   
          12. Catch(RuntimeException ex){   
          13.   //回滾事務   
          14.   con.rollback();     
          15. }   
          16. finally{   
          17.   //釋放資源   
          18.   closeCon();   
          19. }   
          20. }  

          Spring保證在methodB方法中所有的調用都獲得到一個相同的連接。在調用methodB時,沒有一個存在的事務,所以獲得一個新的連接,開啟了一個新的事務。

           

          單獨調用MethodA時,在MethodA內又會調用MethodB.

          執行效果相當于

          代碼
          1. main{   
          2.    Connection con = null;   
          3.    try{   
          4.       con = getConnection();   
          5.       methodA();   
          6.       con.commit();   
          7. }   
          8. cathc(RuntimeException ex){   
          9.  con.rollback();   
          10. }   
          11. finally{   
          12.   closeCon();   
          13. }    
          14. }  

          調用MethodA時,環境中沒有事務,所以開啟一個新的事務.
          當在MethodA中調用MethodB時,環境中已經有了一個事務,所以methodB就加入當前事務。

           

          PROPAGATION_SUPPORTS 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行。但是對于事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。

          代碼
          1. //事務屬性 PROPAGATION_REQUIRED    
          2. methodA(){   
          3.   methodB();   
          4. }   
          5.   
          6. //事務屬性 PROPAGATION_SUPPORTS    
          7. methodB(){   
          8.   ……   
          9. }  

          單純的調用methodB時,methodB方法是非事務的執行的。
          當調用methdA時,methodB則加入了methodA的事務中,事務地執行。

           

          PROPAGATION_MANDATORY 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常。

          代碼
          1. //事務屬性 PROPAGATION_REQUIRED    
          2. methodA(){   
          3.   methodB();   
          4. }   
          5.   
          6. //事務屬性 PROPAGATION_MANDATORY    
          7. methodB(){   
          8.   ……   
          9. }  

          當單獨調用methodB時,因為當前沒有一個活動的事務,則會拋出異常
          throw new IllegalTransactionStateException("Transaction propagation 'mandatory' but no existing transaction found");

           

          當調用methodA時,methodB則加入到methodA的事務中,事務地執行。

          PROPAGATION_REQUIRES_NEW 總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。

          代碼
          1. //事務屬性 PROPAGATION_REQUIRED    
          2. methodA(){   
          3.   doSomeThingA();   
          4. methodB();   
          5. doSomeThingB();   
          6. }   
          7.   
          8. //事務屬性 PROPAGATION_REQUIRES_NEW    
          9. methodB(){   
          10.   ……   
          11. }  

          當單獨調用methodB時,相當于把methodb聲明為REQUIRED。開啟一個新的事務,事務地執行。

           

          當調用methodA時

          代碼
          1. main(){   
          2.   methodA();   
          3. }  
          情況有些大不一樣.相當于下面的效果。
          代碼
          1. main(){   
          2.  TransactionManager tm = null;   
          3. try{   
          4.   //獲得一個JTA事務管理器   
          5.    tm = getTransactionManager();   
          6.    tm.begin();//開啟一個新的事務   
          7.    Transaction ts1 = tm.getTransaction();   
          8.    doSomeThing();   
          9.    tm.suspend();//掛起當前事務   
          10.    try{   
          11.      tm.begin();//重新開啟第二個事務   
          12.      Transaction ts2 = tm.getTransaction();   
          13.      methodB();   
          14.      ts2.commit();//提交第二個事務   
          15.         
          16.    }   
          17.   Catch(RunTimeException ex){   
          18.      ts2.rollback();//回滾第二個事務   
          19.   }   
          20.   finally{   
          21.     //釋放資源   
          22.   }   
          23.    //methodB執行完后,復恢第一個事務   
          24.    tm.resume(ts1);   
          25. doSomeThingB();   
          26.    ts1.commit();//提交第一個事務   
          27. }   
          28. catch(RunTimeException ex){   
          29.   ts1.rollback();//回滾第一個事務   
          30. }   
          31. finally{   
          32.   //釋放資源   
          33. }   
          34. }  

          在這里,我把ts1稱為外層事務,ts2稱為內層事務。從上面的代碼可以看出,ts2與ts1是兩個獨立的事務,互不相干。Ts2是否成功并不依賴于ts1。如果methodA方法在調用methodB方法后的doSomeThingB方法失敗了,而methodB方法所做的結果依然被提交。而除了methodB之外的其它代碼導致的結果卻被回滾了。
          使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作為事務管理器。

           

          PROPAGATION_NOT_SUPPORTED 總是非事務地執行,并掛起任何存在的事務。

          代碼
          1. //事務屬性 PROPAGATION_REQUIRED    
          2. methodA(){   
          3.   doSomeThingA();   
          4. methodB();   
          5. doSomeThingB();   
          6. }   
          7.   
          8. //事務屬性 PROPAGATION_NOT_SUPPORTED    
          9. methodB(){   
          10.   ……   
          11. }  

          當單獨調用methodB時,不啟用任何事務機制,非事務地執行。
          當調用methodA時,相當于下面的效果

           

          代碼
          1. main(){   
          2.  TransactionManager tm = null;   
          3. try{   
          4.   //獲得一個JTA事務管理器   
          5.    tm = getTransactionManager();   
          6.    tm.begin();//開啟一個新的事務   
          7.    Transaction ts1 = tm.getTransaction();   
          8.    doSomeThing();   
          9.    tm.suspend();//掛起當前事務   
          10.      methodB();   
          11.    //methodB執行完后,復恢第一個事務   
          12.    tm.resume(ts1);   
          13. doSomeThingB();   
          14.    ts1.commit();//提交第一個事務   
          15. }   
          16. catch(RunTimeException ex){   
          17.   ts1.rollback();//回滾第一個事務   
          18. }   
          19. finally{   
          20.   //釋放資源   
          21. }   
          22. }  
          使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作為事務管理器。

           

          PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常

          代碼
          1. //事務屬性 PROPAGATION_REQUIRED    
          2. methodA(){   
          3.   doSomeThingA();   
          4. methodB();   
          5. doSomeThingB();   
          6. }   
          7.   
          8. //事務屬性 PROPAGATION_NEVER    
          9. methodB(){   
          10.   ……   
          11. }  
          單獨調用methodB,則非事務的執行。
          調用methodA則會拋出異常
          throw new IllegalTransactionStateException(
          "Transaction propagation 'never' but existing transaction found");

           

          PROPAGATION_NESTED如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行

          這是一個嵌套事務,使用JDBC 3.0驅動時,僅僅支持DataSourceTransactionManager作為事務管理器。需要JDBC 驅動的java.sql.Savepoint類。有一些JTA的事務管理器實現可能也提供了同樣的功能。

          使用PROPAGATION_NESTED,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設為true;
          而nestedTransactionAllowed屬性值默認為false;

          代碼
          1. //事務屬性 PROPAGATION_REQUIRED    
          2. methodA(){   
          3.   doSomeThingA();   
          4. methodB();   
          5. doSomeThingB();   
          6. }   
          7.   
          8. //事務屬性 PROPAGATION_NESTED   
          9. methodB(){   
          10.   ……   
          11. }  

          如果單獨調用methodB方法,則按REQUIRED屬性執行。

           

          如果調用methodA方法,相當于下面的效果

          代碼
          1. main(){   
          2. Connection con = null;   
          3. Savepoint savepoint = null;   
          4. try{   
          5.   con = getConnection();   
          6.   con.setAutoCommit(false);   
          7.   doSomeThingA();   
          8.   savepoint = con2.setSavepoint();   
          9.   try  
          10.       methodB();   
          11.   }catch(RuntimeException ex){   
          12.      con.rollback(savepoint);   
          13.   }   
          14.   finally{   
          15.     //釋放資源   
          16.   }   
          17.   
          18.   doSomeThingB();   
          19.   con.commit();   
          20. }   
          21. catch(RuntimeException ex){   
          22.   con.rollback();   
          23. }   
          24. finally{   
          25.   //釋放資源   
          26. }   
          27. }  
          當methodB方法調用之前,調用setSavepoint方法,保存當前的狀態到savepoint。如果methodB方法調用失敗,則恢復到之前保存的狀態。但是需要注意的是,這時的事務并沒有進行提交,如果后續的代碼(doSomeThingB()方法)調用失敗,則回滾包括methodB方法的所有操作。

           

          嵌套事務一個非常重要的概念就是內層事務依賴于外層事務。外層事務失敗時,會回滾內層事務所做的動作。而內層事務操作失敗并不會引起外層事務的回滾

          PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區別:它們非常類似,都像一個嵌套事務,如果不存在一個活動的事務,都會開啟一個新的事務。使用PROPAGATION_REQUIRES_NEW時,內層事務與外層事務就像兩個獨立的事務一樣,一旦內層事務進行了提交后,外層事務不能對其進行回滾。兩個事務互不影響。兩個事務不是一個真正的嵌套事務。同時它需要JTA事務管理器的支持。
          使用PROPAGATION_NESTED時,外層事務的回滾可以引起內層事務的回滾。而內層事務的異常并不會導致外層事務的回滾,它是一個真正的嵌套事務。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED時,需要JDBC 3.0以上驅動及1.4以上的JDK版本支持。其它的JTA TrasactionManager實現可能有不同的支持方式。

          PROPAGATION_REQUIRED應該是我們首先的事務傳播行為。它能夠滿足我們大多數的事務需求。

          posted @ 2007-11-01 16:13 hk2000c 閱讀(292) | 評論 (0)編輯 收藏

          Spring通過AOP實現聲明式事務管理。通常通過TransactionProxyFactoryBean設置Spring事務代理。我們需要一個目標對象包裝在事務代理中。這個目標對象一般是一個普通Java對象的bean。當我們定義TransactionProxyFactoryBean時,必須提供一個相關的 PlatformTransactionManager的引用和事務屬性。 事務屬性含有上面描述的事務定義。

          PlatformTransactionManager

          HibernateTransactionManager需要一個SessionFactory的引用

          JtaTransactionManager

          一.把事務放置在了DAO層:

          <!—hibernateTransactionManager-->

          <bean id="transactionManager"

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

                 <property name="sessionFactory">

                        <ref local="sessionFactory" />

                 </property>

          </bean>

          <!—DAO層接口實現-->

          <bean id="companyDAOTarget"

                 class="com.vstsoft.querycompany.dao.impl.CompanyDAOImpl">

                 <property name="sessionFactory">

                        <ref local="sessionFactory" />

                 </property>

          </bean>

          <!—springDAO層的事務代理-->

          <bean id="companyDAOProxy"

                 class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

                 <property name="transactionManager">

                        <ref bean="transactionManager" />

                 </property>

                 <property name="target">

                        <ref local="companyDAOTarget" />

                 </property>

                 <property name="transactionAttributes">

                        <props>

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

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

                        <prop key="find*">

                        PROPAGATION_REQUIRED,readOnly

                        </prop>

                        </props>

                 </property>

          </bean>

          <!—業務層接口實現,把DAO注入到Service里面-->

          <bean name="companyManageTarget"

                 class="com.vstsoft.querycompany.service.impl.CompanyManageTarget">

                 <property name="companyDAO">

                        <ref bean="companyDAOProxy" />

                 </property>

          </bean>

          <!—springService層的代理-->

          <bean id="companyManageProxy"

                 class="org.springframework.aop.framework.ProxyFactoryBean">

                 <property name="proxyInterfaces">

                        <value>com.vstsoft.querycompany.service.CompanyManage</value>

                 </property>

                 <property name="target">

                        <ref bean="companyManageTarget" />

                 </property>

          </bean>

          <!—配置struts訪問,把service層注入到action里面-->

          <bean name="/company"

                 class="com.vstsoft.querycompany.web.action.CompanyAction" singleton="false">

                 <property name="companyManage">

                        <ref local="companyManageProxy" />

                 </property>

          </bean>

          二.把事務放置在了Service層:

          <!—jtaTransactionManager-->

          <bean id="jtaTransactionManager"

                 class="org.springframework.transaction.jta.JtaTransactionManager" />

          <!—DAO層接口實現-->

          <bean id="companyDAOTarget"

                 class="com.vstsoft.querycompany.dao.impl.CompanyDAOImpl">

                 <property name="sessionFactory">

                        <ref local="sessionFactory" />

                 </property>

          </bean>

          <!—springDAO層的代理-->

          <bean id="companyDAOProxy"

                 class="org.springframework.aop.framework.ProxyFactoryBean">

                 <property name="proxyInterfaces">

                        <value>com.vstsoft.querycompany.dao.CompanyDAO</value>

                 </property>

                 <property name="target">

                        <ref bean="companyDAOTarget" />

                 </property>

          </bean>

          <!—業務層接口實現,把DAO注入到Service里面-->

          <bean name="companyManageTarget"

                 class="com.vstsoft.querycompany.service.impl.CompanyManageTarget">

                 <property name="companyDAO">

                        <ref bean="companyDAOProxy" />

                 </property>

          </bean>

          <!—spring代理業務層的事務管理-->

          <bean id="companyManageProxy"

                 class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

                 <property name="transactionManager">

                        <ref local="jtaTransactionManager" />

                 </property>

                 <property name="transactionAttributes">

                        <props>

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

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

                        </props>

                 </property>

                 <property name="target">

                        <ref bean="companyManageTarget" />

                 </property>

          </bean>

          <!—配置struts訪問,把service層注入到action里面-->

          <bean name="/company"

                 class="com.vstsoft.querycompany.web.action.CompanyAction" singleton="false">

                 <property name="companyManage">

                        <ref local="companyManageProxy" />

                 </property>

          </bean>

          service層的接口實現CompanyManageImpl里面有個setData方法:按順序執行數據查詢,數據刪除,數據插入數據庫行為,如果哪一步出異常(運行時異常),事務回滾,只有所有行為都沒成功,事務提交。

          posted @ 2007-11-01 15:38 hk2000c 閱讀(5379) | 評論 (0)編輯 收藏

          spring自建事務管理模塊。而且這個事務管理是一個抽象設計,可以應用到很多場合,包括普通的DataSource,jta,jms和hibernate上。 

          要正確使用spring的事務,首先需要了解spring在事務設計上的一些概念 
          統觀spring事務,圍繞著兩個核心PlatformTransactionManager和TransactionStatus

          PlatformTransactionManager 直譯過來就是平臺相關事務,這里的平臺指的是“事務源”,包括剛才我說的DataSource,jta等等。這些無一不是一個事務源。廣義的說,凡是可以完成事務性操作的對象,都可以設計出相對應的PlatformTransactionManager,只要這個事務源支持commit,rollback 和getTransaction語意。

          查看spring代碼,可以發現這些manager實現事務,就是調用事務源的事務操作方法

          比如

          HibernateTransactionManager
          java代碼:

          protected void doCommit(DefaultTransactionStatus status) {
          HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
          if (status.isDebug()) {
          logger.debug("Committing Hibernate transaction on session [" +
          txObject.getSessionHolder().getSession() + "]";
          }
          try {
          txObject.getSessionHolder().getTransaction().commit();
          }
          ...

          }

           

          jdbc 的DataSourceTransactionManager
          java代碼:

          protected void doCommit(DefaultTransactionStatus status) {
          DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
          Connection con = txObject.getConnectionHolder().getConnection();
          if (status.isDebug()) {
          logger.debug("Committing JDBC transaction on connection [" + con + "]";
          }
          try {
          con.commit();
          }
          ...
          }

           

          那么PlatformTransactionManager以什么依據處理事務呢?
          是TransactionStatus
          查看api發現這個接口有三個方法
          isNewTransaction() ,isRollbackOnly(),setRollbackOnly()
          PlatformTransactionManager 就是根據前兩個方法決定是否要創建一個新事務,是要遞交還是回滾。至于第三個方法是改變事務當前狀態的,很多地方都要用到,偏偏 PlatformTransactionManager自身好像不怎么用,畢竟事務狀態的改變是由程序員代碼決定的,不需要一個manager多管閑事。

          總結上面所說的,spring的事務由PlatformTransactionManager管理,manager最后調用事務源的方法來實現一個事務過程。而manager通過TransactionStatus 來決定如何實現。

          接下去說spring事務中的TransactionTemplate和TransactionInterceptor

          TransactionTemplate 其實和spring中其他的template的作用類似,起到化簡代碼的作用,不要被它那么長的名字嚇倒了,事實上這個template并不是什么非常核心的對象。如果比較學究派的,可以去看看template設計模式,在此就不再對此贅述了。
          為什么要有TransactionTemplate?先來看看如果沒有TransactionTemplate,我們的代碼該怎么寫

          先來看看spring reference中的一段代碼
          java代碼:

          DefaultTransactionDefinition def = new DefaultTransactionDefinition()
          def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

          TransactionStatus status = transactionManager.getTransaction(def);

          try {
          // execute your business logic here
          } catch (MyException ex) {
          transactionManager.rollback(status);
          throw ex;
          }
          transactionManager.commit(status);

          這是直接使用transactionManager的例子,可以看到真正執行business logic 的地方是在try當中那段,前后的代碼都是為了完成事務管理的。如果每個business logic都要寫上那么一段,我肯定是瘋了。我們翻出TransactionTemplate的代碼看看他怎么化簡了我們的代碼

          java代碼:

          public Object execute(TransactionCallback action) throws TransactionException {
          TransactionStatus status = this.transactionManager.getTransaction(this);
          Object result = null;
          try {
          result = action.doInTransaction(status);
          }
          catch (RuntimeException ex) {
          // transactional code threw application exception -> rollback
          rollbackOnException(status, ex);
          throw ex;
          }
          catch (Error err) {
          // transactional code threw error -> rollback
          rollbackOnException(status, err);
          throw err;
          }
          this.transactionManager.commit(status);
          return result;
          }


          同上面的代碼如出一轍,前后是事務處理代碼,當中那段result = action.doInTransaction(status);是我們的應用代碼。至于action是什么,全看各位的需要了。但是有一點要主要,如果利用TransactionTemplate,那么他不管你扔出什么異常都會回滾事務,但是回滾的是哪個事務呢?繼續挖代碼
          java代碼:

          private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
          if (logger.isDebugEnabled()) {
          logger.debug("Initiating transaction rollback on application exception", ex);
          }
          try {
          this.transactionManager.rollback(status);
          }
          catch (RuntimeException ex2) {
          logger.error("Application exception overridden by rollback exception", ex);
          throw ex2;
          }
          catch (Error err) {
          logger.error("Application exception overridden by rollback error", ex);
          throw err;
          }
          }

          真相大白,是對template所持有的某個transactionManager進行回滾。所以如果你的應用代碼用的是事務源a的一些資源,比如到服務器 a的一個datasource,但是你的transactionManager管理的是另一些資源,比如服務器b的一個datasource,代碼鐵定不會正常運行

          特別是在一些多事務源的程序里,這點千萬不能搞錯。如果多個事務源之間要完成全局事務,還是老老實實用分布式事務管理服務吧(jta)

          那么TransactionInterceptor是干什么的?這個是spring 的聲明式事務的支持方式。因為用 TransactionTemplate要硬編碼,而且調整事務策略很麻煩(不是說不能調。舉個例子原來程序拋出異常A需要回滾,現在不需要要,我就可以把a catch吃掉。這時候template就不會回滾了。但是每次調整都要重寫編碼。)而用TransactionInterceptor就可以將這些調整寫在配置中。我們再來挖TransactionInterceptor的代碼

          java代碼:

          public Object invoke(MethodInvocation invocation) throws Throwable {
          // Work out the target class: may be null.
          // The TransactionAttributeSource should be passed the target class
          // as well as the method, which may be from an interface
          Class targetClass = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;

          // Create transaction if necessary
          TransactionInfo txInfo = createTransactionIfNecessary(invocation.getMethod(), targetClass);

          Object retVal = null;
          try {
          // This is an around advice.
          // Invoke the next interceptor in the chain.
          // This will normally result in a target object being invoked.
          retVal = invocation.proceed();
          }
          catch (Throwable ex) {
          // target invocation exception
          doCloseTransactionAfterThrowing(txInfo, ex);
          throw ex;
          }
          finally {
          doFinally(txInfo);
          }
          doCommitTransactionAfterReturning(txInfo);

          return retVal;
          }

          萬變不離其宗。

          所以使用spring的事務管理需要作這些事
          1,設置好事務源,比如DataSource,hibernate的session。如果有多個事務源要考慮他們之間是否有全局事務,如果有,老老實實用jta,否則就需要自己寫一個manager了
          2,設置manager,根據你的事務源選擇對應的PlatformTransactionManager
          3,選擇實現事物的方式,用template還是interceptor。用template代碼直觀點,但是template所管轄的manager和你應用代碼所用的事務源要一致。如果用interceptor千萬注意,一定要調用interceptor那個bean,而不是原始的那個target。在壇子上我已經看到至少有兩個朋友說spring事物不起作用,從配置和代碼上看都正確,這時要好好查查,調用的bean是哪一個。
          4,這個是設計問題了,推薦事務處于一個較高層次,比如service上的某個函數,而底層的dao可以不考慮事務,否則可能會出現事務嵌套,增加程序復雜度。

          posted @ 2007-11-01 01:36 hk2000c 閱讀(294) | 評論 (0)編輯 收藏

          僅列出標題
          共11頁: 上一頁 1 2 3 4 5 6 7 8 9 下一頁 Last 
          主站蜘蛛池模板: 井陉县| 通州市| 时尚| 八宿县| SHOW| 唐山市| 昔阳县| 化隆| 蒲城县| 小金县| 永川市| 崇义县| 开化县| 平罗县| 英山县| 双辽市| 神木县| 长岛县| 乐清市| 眉山市| 社会| 平度市| 阆中市| 逊克县| 泰安市| 县级市| 吉隆县| 临江市| 萝北县| 开封市| 乐清市| 福州市| 泰宁县| 庆元县| 大丰市| 闵行区| 东丽区| 镇宁| 威远县| 夏津县| 宜兰县|