天天看點

主流架構二:Spring(4)動态代理實作事務控制一、事務控制二、動态代理回顧三、動态代理實作事務控制

主流架構二:Spring(4)動态代理實作事務控制

  • 一、事務控制
    • 1.ConnectionUtils
    • 2.TransactionManager
    • 3.新的問題
  • 二、動态代理回顧
    • 2.動态代理的作用
    • 3.動态代理的分類:
      • (1)基于接口的動态代理
      • (2)基于子類的動态代理
  • 三、動态代理實作事務控制
    • 1.用于建立service代理對象的factory
    • 2.配置依賴注入
    • 3.service層與測試類

一、事務控制

事務我們在java web中有涉及。Spring中 我們使用了 connection 對象的 setAutoCommit(true)此方式控制事務,如果我們每次都執行一條 sql 語句,沒有問題,但是如果業務方法一次要執行多條 sql語句,這種方式就無法實作功能了。

主流架構二:Spring(4)動态代理實作事務控制一、事務控制二、動态代理回顧三、動态代理實作事務控制

解決辦法:

讓業務層來控制事務的送出和復原。(這個我們之前已經在 web 階段講過了)

改造後的業務層實作類:

用 注:此處沒有使用 spring 的 的IOC.

/**
* 賬戶的業務層實作類
*/
public class AccountServiceImpl implements IAccountService {

	private IAccountDao accountDao = new AccountDaoImpl();
	
	@Override
	public List<Account> findAllAccount() {
		List<Account> accounts = null;
		try {
			TransactionManager.beginTransaction();
			accounts = accountDao.findAll();
			TransactionManager.commit();
			return accounts;
		} catch (Exception e) {
			TransactionManager.rollback();
			e.printStackTrace();
		}finally {
			TransactionManager.release();
		}
		return null;
	}
	......
}
           

下面來看一下這兩個工具類 ConnectionUtils 和 TransactionManager

1.ConnectionUtils

連接配接的工具類,它用于從資料源中擷取一個連接配接,并且實作和線程綁定,是以之後我們擷取目前線程的連接配接便可以直接connectionUtils.getThreadConnection()

/**
 * 連接配接的工具類,它用于從資料源中擷取一個連接配接,并且實作和線程綁定
 */
public class ConnectionUtils {

    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 擷取目前線程上的連接配接
     * @return
     */
    public Connection getThreadConnection() {

            try {
                //1.先從ThreadLocal上擷取
                Connection connection = tl.get();
                //2.判斷目前線程上是否有連接配接
                if (connection == null) {
                    //3.從資料源中擷取一個連接配接,并且存入ThreadLocal中
                    connection = dataSource.getConnection();
                    tl.set(connection);
                }
                //4.傳回目前線程上的連接配接
                return connection;
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
    }

    /**
     * 把連結和線程解綁
     */
    public void removeConnection() {
        tl.remove();
    }
}
           

2.TransactionManager

和事務管理相關的工具類,它包含了:開啟事務,送出事務,復原事務和釋放連接配接(總管理)

例如:connectionUtils.getThreadConnection().setAutoCommit(false); 就等價于connection.setAutoCommit(false);

**
 * 和事務管理相關的工具類,它包含了:開啟事務,送出事務,復原事務和釋放連接配接
 * @author Mango
 */
public class TransactionManager {

    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 開啟事務
     */
    public void beginTransaction() {
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 送出事務
     */
    public void commit() {
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 復原事務
     */
    public void rollback() {
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 釋放連接配接
     */
    public void release() {
        try {
            connectionUtils.getThreadConnection().close();//還回連接配接池中
            connectionUtils.removeConnection();//連接配接與線程解綁
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
           

3.新的問題

上一小節的代碼,通過對業務層改造,已經可以實作事務控制了,但是由于我們添加了事務控制,也産生了一

個新的問題:

@Override
    public List<Account> findAllAccount() {
        try {
            //1.開啟事務
            txManager.beginTransaction();
            //2.執行操作
            List<Account> accounts = accountDao.findAllAccount();
            //3.送出事務
            txManager.commit();
            //4.傳回結果
            return accounts;
        }catch (Exception e){
            //5.復原操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.釋放連接配接
            txManager.release();
        }

    }

    @Override
    public Account findAccountById(Integer accountId) {
        try {
            //1.開啟事務
            txManager.beginTransaction();
            //2.執行操作
            Account account = accountDao.findAccountById(accountId);
            //3.送出事務
            txManager.commit();
            //4.傳回結果
            return account;
        }catch (Exception e){
            //5.復原操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.釋放連接配接
            txManager.release();
        }
    }
           

業務層方法變得臃腫了,裡面充斥着很多重複代碼。并且業務層方法和事務控制方法耦合了。

試想一下,如果我們此時送出,復原,釋放資源中任何一個方法名變更,都需要修改業務層的代碼,況且這還隻是一個業務層實作類,而實際的項目中這種業務層實作類可能有十幾個甚至幾十個。

二、動态代理回顧

主流架構二:Spring(4)動态代理實作事務控制一、事務控制二、動态代理回顧三、動态代理實作事務控制

1、 動态代理的特點

(1)位元組碼随用随建立,随用随加載。

(2)它與靜态代理的差別也在于此。因為靜态代理是位元組碼一上來就建立好,并完成加載。

(3)裝飾者模式就是靜态代理的一種展現。

2.動态代理的作用

**不修改源碼的基礎上對方法增強**
           

3.動态代理的分類:

(1)基于接口的動态代理

(1)基于接口的動态代理:

涉及的類:Proxy

提供者:JDK官方

如何建立代理對象:

使用proxy類中的newProxyInstance方法(要求:被代理的類至少實作其一個接口(例如IProducer,而不能用Producer來實作),如果沒有就不能使用)

newProxyInstance方法
	參數:1.ClassLoader:類加載器
		它是用于加載 代理對象位元組碼的。被代理對象的類加載器(固定寫法)。
		
		2Interfaces:位元組碼數組
		它是用于讓代理對象和被代理對象有相同的方法,實作相同的接口。(固定寫法)
		
		3.InvocationHandler:用于提供增強的代碼
		它是讓我們寫如何代理。我們一般都是寫一個該接口的實作類,通常情況下都是匿名内部類,但不是必須
		此接口的實作類都是誰用誰寫。
           
/**
 * 模拟一個service
 */
public class Client {
    public static void main(String[] args) {
    
        final Producer producer = new Producer();//必須是最終的
        
                IProducer proxyProducer =(IProducer) Proxy.newProxyInstance(
                參數(1)producer.getClass().getClassLoader(),
                參數(2)producer.getClass().getInterfaces(),
                參數(3)new InvocationHandler() {
                
                    /**
                     * 作用:執行被代理對象的任何接口的方法都會經過該方法
                     * 方法參數的含義:
                     * @param proxy  代理對象的引用
                     * @param method 目前執行的方法
                     * @param args   目前執行方法所需的參數
                     * @return       和被代理對象方法有相同的傳回值
                     * @throws Throwable
                     */
                     
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增強方法的代碼
                        Object returnValue = null;
                        //1.擷取方法執行的參數
                        Float money = (Float) args[0];
                        //2.判斷目前方法是不是銷售
                        if("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money * 0.8f);
                        }
                        return returnValue;//方法執行一遍便有相同傳回值
                    }
                });

        proxyProducer.saleProduct(10000f);
    }
}
           

(2)基于子類的動态代理

基于子類的動态代理

提供者:第三方的 CGLib,如果報 asmxxxx 異常,需要導入 asm.jar。需要導入jar包

要求:被代理類不能用 final 修飾的類(最終類)。

使用 CGLib 的 的 Enhancer 類建立代理對象

(2)基于子類的動态代理:

涉及的類:Enhancer

提供者:第三方cglib庫

如何建立代理對象:
        使用Enhancer中的create方法(要求:被代理的類不能是最終類)

     create中的參數:
        1.class:位元組碼
            用于指定被代理對象的位元組碼(這裡Producer被代理)
        2.callback:用于提供增強的代碼
            一般寫的都是該接口的子接口實作類:MenthInterceptor
           
/**
 * 模拟一個消費者
 */
public class Client {
    public static void main(String[] args) {
    
        final Producer producer = new Producer();

        Producer cglibProducer =(Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             *
             * @param o
             * @param method
             * @param objects
                以上三個參數和基于接口的動态代理中的invoke方法的參數時一樣的

             * @param methodProxy 目前執行方法的代理對象
             * @return
             * @throws Throwable
             */
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //提供增強方法的代碼
                Object returnValue = null;
                //1.擷取方法執行的參數
                Float money = (Float) objects[0];
                //2.判斷目前方法是不是銷售
                if("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money * 0.8f);
                }
                return returnValue;//方法執行一遍便有相同傳回值
            }
        });
       cglibProducer.saleProduct(10000f);
}
}
           

三、動态代理實作事務控制

1.用于建立service代理對象的factory

解決案例中的問題:

建立客戶業務層對象工廠(當然也可以建立其他業務層對象,隻不過我們此處不做那麼繁瑣)

/**
 * 用于建立service代理對象的factory
 * @author Mango
 */
public class BeanFactory {

    private IAccountService accountService;

    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public IAccountService getAccountService() {
        return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 添加事務的支援
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object returnValue = null;
                        try {
                            //1.開啟事務
                            transactionManager.beginTransaction();
                            //2.執行操作
                            returnValue = method.invoke(accountService, args);
                            //3.送出事務
                            transactionManager.commit();
                            //4.傳回結果
                            return returnValue;
                        } catch (Exception e) {
                            //5.復原
                            transactionManager.rollback();
                            throw new RuntimeException(e);
                        } finally {
                            //6.釋放連接配接
                            transactionManager.release();
                        }
                    }
                });
    }
}
           

2.配置依賴注入

<!--配置代理的service對象-->
    <bean id="proxyAccountService" factory-bean="beanfactory" factory-method="getAccountService"></bean>

    <!--配置beanfactory-->
    <bean id="beanfactory" class="com.itheima.factory.BeanFactory">
        <!--注入service-->
        <property name="accountService" ref="accountService"></property>
        <!--注入事務管理器-->
        <property name="transactionManager" ref="transactionManger"></property>
    </bean>
           

3.service層與測試類

當我們改造完成之後,業務層用于控制事務的重複代碼就都可以删掉了。

/**
 * 賬戶的業務層實作類
 */
public class AccountServiceImpl implements IAccountService{

    private IAccountDao accountDao;


    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    
	@Override
    public void transfer(String souceName, String targetName, Float money) {
        System.out.println("transfer......");
        //2.執行操作
            //1.根據名稱查詢轉出賬戶
            Account source = accountDao.findAccountByName(souceName);
            //2.根據名稱查詢轉入賬戶
            Account target = accountDao.findAccountByName(targetName);
            //3.轉出賬戶減錢
            source.setMoney(source.getMoney() - money);
            //4.轉入賬戶加錢
            target.setMoney(target.getMoney() + money);
            //5.更新轉出賬戶
            accountDao.updateAccount(source);
            //6.更新轉入賬戶
            accountDao.updateAccount(target);
        }
}        
           

是以測試類中:

/**
 * 使用Junit單元測試:測試我們的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    @Qualifier("proxyAccountService")//使用BeanFactory中的代理AccountService來執行
    private  IAccountService as;

    @Test
    public void testTransfer() {
        as.transfer("aaa","bbb", 100f);
    }
}