java事務全解析(六)--使用動態代理(Dynamic Proxy)完成事務

本系列上一篇文章中,我們講到了使用Template模式進行事務管理,這固然是一種很好的方法,但是不那麼完美的地方在於我們依然需要在service層中編寫和事務處理相關的代碼,即我們需要在service層中聲明一個TransactionTemplate。在本篇文章中,我們將使用Java提供的動態代理(Dynamic Proxy)功能來完成事務處理,你將看到無論是在service層還是DAO層都不會有事務處理代碼,即他們根本就意識不到事務處理的存在。使用動態代理完成事務處理也是AOP的一種典型應用。

 

這是一個關於Java事務處理的系列文章,請通過以下方式下載github源代碼:

Git clone https://github.com/davenkin/java_transaction_workshop.git

 

Java動態代理的基本原理爲:被代理對象需要實現某個接口(這是前提),代理對象會攔截對被代理對象的方法調用,在其中可以全然拋棄被代理對象的方法實現而完成另外的功能,也可以在被代理對象方法調用的前後增加一些額外的功能。在本篇文章中,我們將攔截service層的transfer方法,在其調用之前加入事務準備工作,然後調用原來的transfer方法,之後根據transfer方法是否執行成功決定commit還是rollback。

 

首先定義一個TransactionEnabledProxyManager類:

  1. public class TransactionEnabledProxyManager  
  2. {  
  3.     private TransactionManager transactionManager;  
  4.         
  5.     public TransactionEnabledProxyManager(TransactionManager transactionManager)  
  6.     {  
  7.         
  8.         this.transactionManager = transactionManager;  
  9.     }  
  10.         
  11.     public Object proxyFor(Object object)  
  12.     {  
  13.         return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new TransactionInvocationHandler(object, transactionManager));  
  14.     }  
  15. }  
  16.         
  17. class TransactionInvocationHandler implements InvocationHandler  
  18. {  
  19.     private Object proxy;  
  20.     private TransactionManager transactionManager;  
  21.         
  22.     TransactionInvocationHandler(Object object, TransactionManager transactionManager)  
  23.     {  
  24.         this.proxy = object;  
  25.         this.transactionManager = transactionManager;  
  26.     }  
  27.         
  28.     public Object invoke(Object o, Method method, Object[] objects) throws Throwable  
  29.     {  
  30.         transactionManager.start();  
  31.         Object result = null;  
  32.         try  
  33.         {  
  34.             result = method.invoke(proxy, objects);  
  35.             transactionManager.commit();  
  36.         } catch (Exception e)  
  37.         {  
  38.             transactionManager.rollback();  
  39.         } finally  
  40.         {  
  41.             transactionManager.close();  
  42.         }  
  43.         return result;  
  44.     }  
  45. }  
public class TransactionEnabledProxyManager
{
    private TransactionManager transactionManager;
      
    public TransactionEnabledProxyManager(TransactionManager transactionManager)
    {
      
        this.transactionManager = transactionManager;
    }
      
    public Object proxyFor(Object object)
    {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new TransactionInvocationHandler(object, transactionManager));
    }
}
      
class TransactionInvocationHandler implements InvocationHandler
{
    private Object proxy;
    private TransactionManager transactionManager;
      
    TransactionInvocationHandler(Object object, TransactionManager transactionManager)
    {
        this.proxy = object;
        this.transactionManager = transactionManager;
    }
      
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable
    {
        transactionManager.start();
        Object result = null;
        try
        {
            result = method.invoke(proxy, objects);
            transactionManager.commit();
        } catch (Exception e)
        {
            transactionManager.rollback();
        } finally
        {
            transactionManager.close();
        }
        return result;
    }
}

通過調用該類的proxyFor方法,傳入需要被代理的對象(本例中爲service對象),返回一個代理對象。此後,在調用代理對象的transfer方法時,會自動調用TransactionIvocationHandler的invoke方法,在該方法中,我們首先開始事務,然後執行:

 

 result = method.invoke(proxy, objects);

 

 

上面一行代碼執行的是原service層的transfer方法,如果方法執行成功則commit,否則rollback事務。

 

由於與事務處理相關的代碼都被轉移到了代理對象中,在service層中我們只需調用DAO即可:

  1. public class BareBankService implements BankService  
  2. {  
  3.     private ConnectionHolderBankDao connectionHolderBankDao;  
  4.     private ConnectionHolderInsuranceDao connectionHolderInsuranceDao;  
  5.        
  6.     public BareBankService(DataSource dataSource)  
  7.     {  
  8.         connectionHolderBankDao = new ConnectionHolderBankDao(dataSource);  
  9.         connectionHolderInsuranceDao = new ConnectionHolderInsuranceDao(dataSource);  
  10.     }  
  11.        
  12.     public void transfer(final int fromId, final int toId, final int amount)  
  13.     {  
  14.         try  
  15.         {  
  16.             connectionHolderBankDao.withdraw(fromId, amount);  
  17.             connectionHolderInsuranceDao.deposit(toId, amount);  
  18.         } catch (Exception e)  
  19.         {  
  20.             throw new RuntimeException();  
  21.         }  
  22.     }  
  23. }  
public class BareBankService implements BankService
{
    private ConnectionHolderBankDao connectionHolderBankDao;
    private ConnectionHolderInsuranceDao connectionHolderInsuranceDao;
     
    public BareBankService(DataSource dataSource)
    {
        connectionHolderBankDao = new ConnectionHolderBankDao(dataSource);
        connectionHolderInsuranceDao = new ConnectionHolderInsuranceDao(dataSource);
    }
     
    public void transfer(final int fromId, final int toId, final int amount)
    {
        try
        {
            connectionHolderBankDao.withdraw(fromId, amount);
            connectionHolderInsuranceDao.deposit(toId, amount);
        } catch (Exception e)
        {
            throw new RuntimeException();
        }
    }
}

如何,上面的BareBankService中沒有任何事務處理的影子,我們只需關注核心業務邏輯即可。

 

然後在客戶代碼中,我們需要先創建代理對象(這在spring中通常是通過配置實現的):

  1. @Test  
  2.     public void transferFailure() throws SQLException  
  3.     {  
  4.         TransactionEnabledProxyManager transactionEnabledProxyManager = new TransactionEnabledProxyManager(new TransactionManager(dataSource));  
  5.         BankService bankService = new BareBankService(dataSource);  
  6.         BankService proxyBankService = (BankService) transactionEnabledProxyManager.proxyFor(bankService);  
  7.       
  8.         int toNonExistId = 3333;  
  9.         proxyBankService.transfer(1111, toNonExistId, 200);  
  10.       
  11.         assertEquals(1000, getBankAmount(1111));  
  12.         assertEquals(1000, getInsuranceAmount(2222));  
  13.     }  
@Test
    public void transferFailure() throws SQLException
    {
        TransactionEnabledProxyManager transactionEnabledProxyManager = new TransactionEnabledProxyManager(new TransactionManager(dataSource));
        BankService bankService = new BareBankService(dataSource);
        BankService proxyBankService = (BankService) transactionEnabledProxyManager.proxyFor(bankService);
    
        int toNonExistId = 3333;
        proxyBankService.transfer(1111, toNonExistId, 200);
    
        assertEquals(1000, getBankAmount(1111));
        assertEquals(1000, getInsuranceAmount(2222));
    }

在上面的測試代碼中,我們首先創建一個BareBankService對象,然後調用transactionEnabledProxyManager的proxyFor方法生成對原BareBankService對象的代理對象,最後在代理對象上調用transfer方法,測試運行成功。

 

可以看到,通過以上動態代理實現,BareBankService中的所有public方法都被代理了,即他們都被加入到事務中。這對於service層中的所有方法都需要和數據庫打交道的情況是可以的,本例即如此(有且只有一個transfer方法),然而對於service層中不需要和數據庫打交道的public方法,這樣做雖然也不會出錯,但是卻顯得多餘。在下一篇文章中,我們將講到使用Java註解(annotation)的方式來聲明一個方法是否需要事務,就像Spring中的Transactional註解一樣。


轉載地址:http://www.davenkin.me/post/2013-02-24/40049235086



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章