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()來回滾,若不手動回滾就會造成事務失效。