Spring筆記9 事務控制的問題&動態代理

https://www.bilibili.com/video/av47952931
p46~55


Account案例中轉賬方法的事務問題

在這裏插入圖片描述
事務控制應該都在業務層,之前的案例中都在持久層,需要修改
寫兩個工具類

2個工具類

ConnectionUtils

/**
 * 連接的工具類,它用於從數據源中獲取一個連接,並且實現和線程的綁定
 */
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 conn = tl.get();
            // 2.判斷當前線程上是否有連接
            if (conn == null) {
                // 3.從數據源中獲取一個連接,並且存入ThreadLocal中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            // 4.返回當前線程上的連接
            return conn;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

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

TransactionManager

/**
 * 和事務管理相關的工具類,它包含了,開啓事務,提交事務,回滾事務和釋放連接
 */
public class TransactionManager {

    private ConnectionUtils connectionUtils;

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

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

    /**
     * 提交事務
     */
    public  void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 回滾事務
     */
    public  void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
     * 釋放連接
     */
    public  void release(){
        try {
            connectionUtils.getThreadConnection().close();// 還回連接池中
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

連接還回連接池中後,還需再把連接和線程解綁,否則下次ConnectionUtils中判斷是否有連接是true,但這個連接是已經關閉的錯誤的連接

注入

	<!-- 配置Connection的工具類 ConnectionUtils -->
    <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
        <!-- 注入數據源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置事務管理器-->
    <bean id="txManager" class="com.itheima.utils.TransactionManager">
        <!-- 注入ConnectionUtils -->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

代碼改造

修改之後不需要在beans.xml中注入dataSource了

    <!-- 配置Connection的工具類 ConnectionUtils -->
    <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
        <!-- 注入數據源-->
        <!-- property name="dataSource" ref="dataSource"></property-->
    </bean>

在AccountDaoImpl中加一個ConnectionUtils

private ConnectionUtils connectionUtils;

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

並且runner獲取連接改爲

runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));

此時,AccountServiceImpl中一個完整的事務流程是

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();
        }
}

但是每個方法都要這樣寫,很臃腫
而且方法的依賴很嚴重(如果TransactionManager中beginTransaction方法名改成beginTransaction1,AccountServiceImpl中每一處用到的都要改)
進一步改造:代理

& 現在的依賴有些亂七八糟,在後面Spring的事務控制中解決

動態代理

描述

在這裏插入圖片描述
特點:字節碼隨用隨創建,隨用隨加載
作用:不修改源碼的基礎上對方法增強

分類:

  • 基於接口的動態代理
  • 基於子類的動態代理

用處如:
連接池close方法關閉時不能真正關閉,還要還回池中。可以使用動態代理對其進行增強,把它還回池裏
解決中文亂碼,request對象的方法增強,用裝飾者模式可以實現,也可以用動態代理實現

基於接口的動態代理

涉及的類:Proxy
提供者:JDK官方
如何創建代理對象:使用Proxy類中的newProxyInstance方法
創建代理對象的要求:被代理類最少實現一個接口,如果沒有則不能使用

newProxyInstance方法的參數:

  • ClassLoader:類加載器
    它是用於加載代理對象字節碼的。和被代理對象使用相同的類加載器。固定寫法
  • Class[]:字節碼數組
    它是用於讓代理對象和被代理對象有相同方法。固定寫法
  • InvocationHandler:用於提供增強的代碼
    寫如何代理。一般都是寫一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的。此接口的實現類都是誰用誰寫

示例

/**
 * 對生產廠家要求的接口
 */
public interface IProducer {

    /**
     * 銷售
     * @param money
     */
    public void saleProduct(float money);

    /**
     * 售後
     * @param money
     */
    public void afterService(float money);
}
/**
 * 一個生產者
 */
public class Producer implements IProducer{

    public void saleProduct(float money){
        System.out.println("銷售產品,並拿到錢:"+money);
    }
    
    public void afterService(float money){
        System.out.println("提供售後服務,並拿到錢:"+money);
    }
    
}
/**
 * 模擬一個消費者
 */
public class Client {

    public static void main(String[] args) {
        final Producer producer = new Producer();

        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                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);
    }
}

使用代理後,消費者付10000,代理提成20%,生產者拿到8000

並沒有對生產者的代碼做任何修改,但是實現了增強
此處即爲基於接口的動態代理

但是有一個問題
如果生產者沒有實現接口,就不能這樣用了,會報代理異常

基於子類的動態代理

要求有第三方jar包的支持

<dependencies>
	<dependency>
		<groupId>cglib</groupId>
		<artifactId>cglib</artifactId>
		<version>2.1_3</version>
	</dependency>
</dependencies>

涉及的類:Enhancer
提供者:第三方cglib庫

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

create方法的參數:

  • Class:字節碼
    用於指定被代理對象的字節碼
  • Callback:用於提供增強的代碼
    寫如何代理。一般是寫一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的
    此接口的實現類都是誰用誰寫
    一般寫的都是該接口的子接口實現類:MethodInterceptor

示例

/**
 * 一個生產者
 */
public class Producer {

    /**
     * 銷售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("銷售產品,並拿到錢:"+money);
    }

    /**
     * 售後
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售後服務,並拿到錢:"+money);
    }
}
package com.itheima.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 模擬一個消費者
 */
public class Client {

    public static void main(String[] args) {
        final Producer producer = new Producer();

        Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 執行被代理對象的任何方法都會經過該方法
             * @param proxy
             * @param method
             * @param args
             *    以上三個參數和基於接口的動態代理中invoke方法的參數是一樣的
             * @param methodProxy :當前執行方法的代理對象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) 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;
            }
        });
        cglibProducer.saleProduct(12000f);
    }
}

使用動態代理實現事務控制

/**
 * 用於創建Service的代理對象的工廠
 */
public class BeanFactory {

    private IAccountService accountService;

    private TransactionManager txManager;
    
    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }
    
    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    /**
     * 獲取Service代理對象
     * @return
     */
    public IAccountService getAccountService() {
        return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 添加事務的支持
                     *
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if("test".equals(method.getName())){
                            return method.invoke(accountService,args);
                        }

                        Object rtValue = null;
                        try {
                            // 1.開啓事務
                            txManager.beginTransaction();
                            // 2.執行操作
                            rtValue = method.invoke(accountService, args);
                            // 3.提交事務
                            txManager.commit();
                            // 4.返回結果
                            return rtValue;
                        } catch (Exception e) {
                            // 5.回滾操作
                            txManager.rollback();
                            throw new RuntimeException(e);
                        } finally {
                            // 6.釋放連接
                            txManager.release();
                        }
                    }
                });
    }
}

並對beans.xml做相應的修改
測試中IAccountService只用Autowird不夠了,還需@Qualifier(“proxyAccountService”)

使用動態代理後,消除了重複代碼,解除了方法的依賴
但是配置變得繁瑣了
更好的方式?——>AOP

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