手寫spring事務框架, 揭秘AOP實現原理。


AOP面向切面編程:主要是通過切面類來提高代碼的復用,降低業務代碼的耦合性,從而提高開發效率。主要的功能是:日志記錄,性能統計,安全控制,事務處理,異常處理等等。

AOP實現原理:aop是通過cglib的動態代理實現的。

   jdk動態代理:利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。

   cglib動態代理:將代理對象類的class文件加載進來,通過ASM字節碼技術修改其字節碼生成子類來處理。

   區別:JDK動態代理只能對實現了接口的類生成代理,而不能針對類 。CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法 。因為是繼承,所以該類或方法最好不要聲明成final ,final可以阻止繼承和多態。

一:AOP運行過程

項目結構

1.1 導入相關包

  <dependencies>
        <!-- 引入Spring-AOP等相關Jar -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1_2</version>
        </dependency>
        <!-- 數據庫連接池 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
    </dependencies>

1.2 配置包掃描和切面代理

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/tx
          http://www.springframework.org/schema/tx/spring-tx.xsd">
    
    <!-- 掃描指定路徑 -->
    <context:component-scan base-package="com.wulei"/>
    <!-- 開啟切面代理 -->
    <aop:aspectj-autoproxy /> 

    <!-- 1. 數據源對象: C3P0連接池
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean> -->

    <!-- 2. JdbcTemplate工具類實例
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean> -->

    <!-- 3. 配置事務
    <bean id="dataSourceTransactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean> -->
</beans>

1.3 編寫AOP切面

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component//注入到spring
@Aspect//申明切面類
public class AopLog {
     
     // aop 編程里面有幾個通知: 前置通知 后置通知 運行通知 異常通知 環繞通知
     @Before("execution(* com.wulei.service.UserService.add(..))")
     public void before() {
     System.out.println("前置通知: 在方法之前執行...");
     }
    
     // 后置通知 在方法運行后執行
     @After("execution(* com.wulei.service.UserService.add(..))")
     public void after() {
         System.out.println("后置通知: 在方法之后執行...");
     }
    
     // 運行通知
     @AfterReturning("execution(* com.wulei.service.UserService.add(..))")
     public void returning() {
         System.out.println("運行通知:");
     }
    
     // 異常通知
     @AfterThrowing("execution(* com.wulei.service.UserService.add(..))")
     public void afterThrowing() {
         System.out.println("異常通知: 異常拋出執行");// 異常被try()cacth{}捕捉到則不執行。
     }
    
     // 環繞通知 在方法之前和之后處理事情
     @Around("execution(* com.wulei.service.UserService.add(..))")
     public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        // 調用方法之前執行
        System.out.println("環繞通知: 調用方法之前執行");
        proceedingJoinPoint.proceed();
        /* 代理調用方法 , 如果調用方法拋出異常就不會執行后面代碼。
         * 
         * 在使用spring事務的時候  service最好不要try, 將異常拋出給aop 異常通知處理回滾!
         * 否則業務邏輯出錯,而aop卻正常執行,就會造成事務失效的情況。
         */
        
         // 調用方法之后執行
         System.out.println("環繞通知: 調用方法之后執行");
     }
}

1.4 編寫Service

@Service
public class UserService {
    public void add() {
        System.out.println("正在添加數據");
        int i = 1/0;
        // 如果出現異常就會觸發AOP異常通知,如果異常被try()catch{}住,則會不觸發異常通知繼續走完環繞通知。
    }
}

1.5 測試

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.add();
    }
}
=================
【控制台輸出】
    前置通知: 在方法之前執行...
    Exception in thread "main" 環繞通知: 調用方法之前執行 正在添加數據 后置通知: 在方法之后執行... 異常通知: 異常拋出執行 java.lang.ArithmeticException: / by zero at com.wulei.service.UserService.add(UserService.java:16)
 

二:手寫編程式事務

2.1 在spring.xml配置好自己的數據源。
2.2 編寫dao層

/*
 CREATE TABLE `t_users` (
   `name` varchar(20) NOT NULL,
   `age` int(5) DEFAULT NULL,
   PRIMARY KEY (`name`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 */
@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public int add(String name, Integer age) {
        String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);";
        int result = jdbcTemplate.update(sql, name, age);
        System.out.println("插入成功");
        return result;
    }
}

2.3 手寫編程式事務具體邏輯

@Component
public class MyTransaction {
    
    // 獲取數據源
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    
    // 開啟事務
    public TransactionStatus begin() {
        // getTransaction()這里的參數是用的事務默認的傳播屬性
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
        System.out.println("開啟事務");
        // 得到事務狀態
        return transaction;
    }

    // 提交事務
    public void commit(TransactionStatus transaction) {
        dataSourceTransactionManager.commit(transaction);
        System.out.println("提交事務");
    }

    // 回滾事務
    public void rollback(TransactionStatus transaction) {
        dataSourceTransactionManager.rollback(transaction);
        System.out.println("事務回滾");
    }
}

2.4 編寫service,然后運行測試。

@Service
public class UserService {

    @Autowired
    private UserDao userDao;
    @Autowired
    private MyTransaction myTransaction;
    
    public void add() {
        TransactionStatus transactionStatus = null;
        try {
            //1. 開啟事務
            transactionStatus = myTransaction.begin();
            userDao.add("test001", 20);
            int i = 1 / 0;
            userDao.add("test002", 21);
            //2. 執行成功就提交事務
            myTransaction.commit(transactionStatus);
        } catch (Exception e) {
            //3. 出現異常就回滾
            myTransaction.rollback(transactionStatus);
        }
    }
}

========================
【控制台輸出】   此時查看數據庫可以發現,由於我們手動回滾所以沒有插入數據。
  前置通知: 在方法之前執行...
  環繞通知: 調用方法之前執行
  開啟事務
  插入成功
  事務回滾
  后置通知: 在方法之后執行...
  環繞通知: 調用方法之后執行
  運行通知:

三:AOP重構編程式事務

3.1 通過aop實現spring事務

@Component
@Aspect
// 基於AOP的環繞通知和異常通知實現Spring事務
public class AopTransaction {

    @Autowired
    private MyTransaction myTransaction;
    
    // 環繞通知  在方法之前和之后處理事情
    @Around("execution(* com.wulei.service.UserService.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        // 開啟事務    調用方法之前執行 
        TransactionStatus transactionStatus = myTransaction.begin();
        proceedingJoinPoint.proceed();// 代理調用方法 注意點: 如果調用方法拋出溢出不會執行后面代碼
        // 提交事務  調用方法之后執行
        myTransaction.commit(transactionStatus);
    }
    // 異常通知
    @AfterThrowing("execution(* com.wulei.service.UserService.add(..))")
    public void afterThrowing() {
        System.out.println("回滾當前事務");
        // 獲取當前事務 直接回滾
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

3.2 編寫service, 然后運行測試

@Service
public class UserService {

    @Autowired
    private UserDao userDao;
    
    public void add() {
        
        userDao.add("test001", 20);
        int i = 1 / 0;
        userDao.add("test002", 21);//try {
        //    userDao.add("test001", 20);
        //    int i = 1 / 0;
        //    userDao.add("test002", 21);
        //} catch (Exception e) {
        //    // 回滾當前事務。
        //    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        //}
    }
}
===================
【控制台輸出】   此時查看數據庫可以發現,觸發異常通知回滾所以沒有插入數據。
  開啟事務
  插入成功
  回滾當前事務

注意:在環繞通知中begin()開啟了事務,如果程序出現了異常,環繞通知就不會commit()提交事務,若此時異常被try捕捉,異常通知又無法rollback()來回滾,若不手動回滾就會造成事務失效。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM