1.背景
2.AOP的概念
AOP:用鳥語解釋就是 面向切面編程,詳細的解釋大家可以看百度百科,
百度百科:https://baike.baidu.com/item/AOP/1332219?fr=aladdin
不過估計看了后還是一頭霧水...
通俗的理解是:假設有方法M1、M2、M3....,現在需要在這些方法中增加新的業務邏輯(如:打印方法名稱、方法參數、執行結果),但是不把這些新的業務邏輯寫到這些方法(M1、M2、M3....)中,這就是AOP的思想;
簡單一句話說就是:在方法中增加新的業務邏輯,但是不修改方法中的代碼。
3.AOP的底層原理
實現方式一:有接口的情況下,使用JDK動態代理
實現方式二:沒有接口情況,使用 CGLIB 動態代理
3.1.JDK動態代理實現AOP思想
需求:假設有方法M1、M2、M3....,現在需要在這些方法中增加新的業務邏輯(如:打印方法名稱、方法參數、執行結果),但是不把這些新的業務邏輯寫到這些方法(M1、M2、M3....)中,這就是AOP的思想;
步驟一:准備准備一個dao接口、dao實現、測試
dao接口

package com.ldp.dao; /** * @Copyright (C) XXXXXXXXXXX科技股份技有限公司 * @Author: lidongping * @Date: 2021-01-12 10:44 * @Description: */ public interface IProductDao { /** * 保存產品 * * @param name * @param price * @return */ int saveProduct(String name, Integer price); /** * 根據id刪除產品 * * @param id * @return */ int deleteProduct(Integer id); /** * 根據id修改產品 * * @param id * @param name * @param price * @return */ int updateProduct(Integer id, String name, Integer price); /** * 根據id查詢產品 * * @return */ String queryProduct(Integer id); }
dao實現

package com.ldp.dao.impl; import com.ldp.dao.IProductDao; /** * @Copyright (C) XXXXXXXXXXX科技股份技有限公司 * @Author: lidongping * @Date: 2021-01-12 10:51 * @Description: */ public class ProductDaoImpl implements IProductDao { @Override public int saveProduct(String name, Integer price) { System.out.println("模擬 saveProduct---------------"); return 1; } @Override public int deleteProduct(Integer id) { System.out.println("模擬 deleteProduct---------------"); return 1; } @Override public int updateProduct(Integer id, String name, Integer price) { System.out.println("模擬 updateProduct---------------"); return 1; } @Override public String queryProduct(Integer id) { System.out.println("模擬 queryProduct---------------"); return "模擬返回產品數據"; } }
dao測試

/** * 沒有代理的測試 */ @Test public void test01() { // 創建到dao實例對象 IProductDao productDao = new ProductDaoImpl(); // 測增加 int saveProduct = productDao.saveProduct("蘋果", 3); System.out.println(saveProduct); // 測試刪除 int deleteProduct = productDao.deleteProduct(12); System.out.println(deleteProduct); // 測試修改 int updateProduct = productDao.updateProduct(11, "修改蘋果", 4); System.out.println(updateProduct); // 測試查詢 String queryProduct = productDao.queryProduct(11); System.out.println(queryProduct); }
步驟二:編寫一個InvocationHandler的實例對象,編寫需要增強的具體功能

package com.ldp.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays; /** * @Copyright (C) XXXXXXXXXXX科技股份技有限公司 * @Author: lidongping * @Date: 2021-01-12 11:01 * @Description: */ public class MyDaoProxy implements InvocationHandler { private Object object; /** * 定義一個有參數的構造方法 */ public MyDaoProxy(Object object) { this.object = object; } /** * 這里使用了反射的機制 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 方法執行前 System.out.println("方法執行前執行.....請求方法名:" + method.getName() + ",請求參數:" + Arrays.toString(args)); // 執行固有的方法(被增強的方法) Object result = method.invoke(object, args); // 方法執行后 System.out.println("方法執行后.....方法執行結果:" + result); return result; } }
步驟三:使用代理對象生成dao實現
// 使用動態代理 創建到dao實例對象 TestProxy.class.getClassLoader()中的TestProxy為當前測試類對象 // IProductDao productDao = new ProductDaoImpl(); Class[] interfaces = {IProductDao.class}; IProductDao productDao = (IProductDao) Proxy.newProxyInstance(TestProxy.class.getClassLoader(), interfaces, new MyDaoProxy(new ProductDaoImpl()));
步驟四:測試代理對象生成的dao

/** * 代理對象的測試 * newProxyInstance(ClassLoader loader, 類<?>[] interfaces, InvocationHandler h) * 返回指定接口的代理類的實例,該接口將方法調用分派給指定的調用處理程序。 */ @Test public void test02() { // 使用動態代理 創建到dao實例對象 TestProxy.class.getClassLoader()中的TestProxy為當前測試類對象 // IProductDao productDao = new ProductDaoImpl(); Class[] interfaces = {IProductDao.class}; IProductDao productDao = (IProductDao) Proxy.newProxyInstance(TestProxy.class.getClassLoader(), interfaces, new MyDaoProxy(new ProductDaoImpl())); // 測增加 int saveProduct = productDao.saveProduct("蘋果", 3); System.out.println(saveProduct); // 測試刪除 int deleteProduct = productDao.deleteProduct(12); System.out.println(deleteProduct); // 測試修改 int updateProduct = productDao.updateProduct(11, "修改蘋果", 4); System.out.println(updateProduct); // 測試查詢 String queryProduct = productDao.queryProduct(11); System.out.println(queryProduct); }
參考資料
jdk8Api:https://www.matools.com/api/java8
3.2.cglib動態代理實現aop思想
步驟一:引入jar包
步驟二:編寫一個普通的需要被增強的方法

package com.ldp.controller; /** * @Copyright (C) XXXXXXXXXXX科技股份技有限公司 * @Author: lidongping * @Date: 2021-01-12 12:07 * @Description: */ public class ProductController { /** * 產品查詢 * * @param name * @param price * @return */ public String queryProduct(String name, Integer price) { System.out.println("模擬查詢中............"); return "香蕉"; } }
步驟三:編寫一個cglib動態代理類

package com.ldp.proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.util.Arrays; /** * @Copyright (C) XXXXXXXXXXX科技股份技有限公司 * @Author: lidongping * @Date: 2021-01-12 11:50 * @Description: */ public class MyDaoCglibProxy implements MethodInterceptor { private Object target; public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 設置回調方法 enhancer.setCallback(this); // 創建代理對象 return enhancer.create(); } /** * 實現MethodInterceptor接口中重寫的方法 * <p> * 回調方法 */ @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 方法執行前 System.out.println("方法執行前執行.....請求方法名:" + method.getName() + ",請求參數:" + Arrays.toString(args)); // 執行固有的方法(被增強的方法) Object result = proxy.invokeSuper(object, args); // 方法執行后 System.out.println("方法執行后.....方法執行結果:" + result); return result; } }
步驟四:測試

/** * CGLIB 代理對象的測試 */ @Test public void test03() { // 創建動態代理 MyDaoCglibProxy cglibProxy = new MyDaoCglibProxy(); // 獲取代理的實例對象 ProductController productController = (ProductController) cglibProxy.getInstance(new ProductController()); System.out.println(productController.queryProduct("香蕉", 100)); }
測試結果如下:
方法執行前執行.....請求方法名:queryProduct,請求參數:[香蕉, 100]
模擬查詢中............
方法執行后.....方法執行結果:香蕉
香蕉
4.AOP的幾個專業術語
AOP的基本概念
1、連接點:可以被增強的方法
2、切入點:實際被增強的方法
3、通知(增強):
3.1.實際增強的邏輯部分叫做通知
3.2.通知類型包括
- 前置通知(執行方法前執行,通常用作參數日志輸出、權限校驗等)
- 后置通知(邏輯代碼執行完,准備執行return的代碼時通知,通常用作執行結果日志輸出、結果加密等)
- 環繞通知(是前置通知和后置通知的綜合,方法執行前和方法執行后都要執行,通常用作方法性能統計、接口耗時、統一加密、解密等)
- 異常通知(相當於try{}catch ()中catch執行的部分,程序拋出異常時執行,通常用作告警處理、事務回滾等)
- 最終通知(相當於try{}catch (Exception e){}finally { }中的finally執行的部分,通常用在關閉資源、清理緩存等業務邏輯中)
4、切面:把通知(增強)應用到切入點的過程
5.AOP實現技術
1、Spring 框架一般都是基於 AspectJ 實現 AOP 操作
(1)AspectJ 不是 Spring 組成部分,獨立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,進行 AOP 操作
2、基於 AspectJ 實現 AOP 操作
(1)基於 xml 配置文件實現
(2)基於注解方式實現(使用)
3、在項目工程里面引入 AOP 相關依賴
4、切入點表達式
(1)切入點表達式作用:知道對哪個類里面的哪個方法進行增強
(2)語法結構: execution([權限修飾符] [返回類型] [類全路徑] [方法名稱]([參數列表]) )
說明:* 號表示通配符,.. 符號表示一個或多個參數
舉例 1:對 com.ldp.controller.ProductController 類里面的 queryProduct 進行增強 execution(* com.ldp.controller.ProductController.queryProduct (..))
舉例 2:對 com.ldp.controller.ProductController 類里面的所有的方法進行增強 execution(* com.ldp.controller.ProductController .* (..))
舉例 3:對 com.ldp.controller 包里面所有以controller結尾的類里面所有方法進行增強 execution(* com.ldp.controller.*Controller .* (..))
6.AOP案例
6.1.AOP 操作(AspectJ 注解)
這里使用spring中的aop實現對controller里日志輸出來加深對aop的理解
實現步驟:
准備工作:
步驟一:創建一個controller類並定義方法

public class UserController { /** * 查詢用戶 */ public Object queryUser(String name, Integer age) { System.out.println("數據查詢中模擬..........."); return "張無忌,18歲"; } }
步驟二:創建一個增強類,編寫增強邏輯,也就是說建立一個切面

public class LogAop { public void method01() { System.out.println("method01-前置通知-增強邏輯-------------"); } public void method02() { System.out.println("method02-后置通知-增強邏輯-------------"); } public void method03() { System.out.println("method03-環繞通知-增強邏輯-------------"); } public void method04() { System.out.println("method04-異常通知-增強邏輯-------------"); } public void method05() { System.out.println("method05-最終通知-增強邏輯-------------"); } }
AOP配置
步驟一:在 spring 配置文件中,開啟注解掃描

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 開啟注解掃描 --> <context:component-scan base-package="com.ldp.aop"></context:component-scan> </beans>
步驟二:增強類上加上注解,以及在方法加不同類型的通知
類上加注解
方法上加不同類型的通知

package com.ldp.aop; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * @Copyright (C) XXXXXXXXXXX科技股份技有限公司 * @Author: lidongping * @Date: 2021-01-14 9:52 * @Description: <p> * 前置通知(執行方法前執行,通常用作參數日志輸出、權限校驗等) * 后置通知(邏輯代碼執行完,准備執行return的代碼時通知,通常用作執行結果日志輸出、結果加密等) * 環繞通知(是前置通知和后置通知的綜合,方法執行前和方法執行后都要執行,通常用作方法性能統計、接口耗時、統一加密、解密等) * 異常通知(相當於try{}catch ()中catch執行的部分,程序拋出異常時執行,通常用作告警處理、事務回滾等) * 最終通知(相當於try{}catch (Exception e){}finally { }中的finally執行的部分,通常用在關閉資源、清理緩存等業務邏輯中) * </p> * 語法結構: execution([權限修飾符] [返回類型] [類全路徑] [方法名稱]([參數列表]) ) */ @Component @Aspect public class LogAop { /** * 前置通知 */ @Before(value = "execution(* com.ldp.controller.*Controller.*(..))") public void method01() { System.out.println("method01-前置通知-增強邏輯-------------"); } /** * 后置通知 */ @AfterReturning(value = "execution(* com.ldp.controller.*Controller.*(..))") public void method02() { System.out.println("method02-后置通知-增強邏輯-------------"); } /** * 環繞通知 */ @Around(value = "execution(* com.ldp.controller.*Controller.*(..))") public void method03() { System.out.println("method03-環繞通知-增強邏輯-------------"); } /** * 異常通知 */ @AfterThrowing(value = "execution(* com.ldp.controller.*Controller.*(..))") public void method04() { System.out.println("method04-異常通知-增強邏輯-------------"); } /** * 最終通知 */ @After(value = "execution(* com.ldp.controller.*Controller.*(..))") public void method05() { System.out.println("method05-最終通知-增強邏輯-------------"); } }
步驟三:controller類上加上注解
步驟四:測試

package com.ldp.test; import com.ldp.controller.UserController; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @Copyright (C) XXXXXXXXXXX科技股份技有限公司 * @Author: lidongping * @Date: 2021-01-14 10:19 * @Description: */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:bean01.xml") public class TestAop { @Autowired private UserController userController; /** * 測試 */ @Test public void test01() { Object queryUser = userController.queryUser("張無忌", 18); System.out.println("queryUser=" + queryUser); } }
注意:當前測試需要關閉環繞通知
當沒有異常的時候,執行結果
method01- @Before-前置通知-增強邏輯-------------
數據查詢中模擬...........
method02- @AfterReturning-后置通知-增強邏輯-------------
method05- @After-最終通知-增強邏輯-------------
queryUser=張無忌,18歲
當有異常的時候
method01- @Before-前置通知-增強邏輯-------------
數據查詢中模擬...........
method04- @AfterThrowing-異常通知-增強邏輯-------------
method05- @After-最終通知-增強邏輯-------------
6.2.XML實現AOP配置(現在很少使用,了解)
步驟一:創建一個普通的增強類和增強方法
public class LogAop03 { public void method01() { System.out.println("-----xml的方式-----------"); } }
步驟二:xml中配置aop

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 開啟注解掃描 --> <context:component-scan base-package="com.ldp.*"></context:component-scan> <!-- 開啟 Aspect 生成代理對象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <bean id="logAop03" class="com.ldp.aop.LogAop03"></bean> <!--配置 aop 增強--> <aop:config> <!--切入點--> <aop:pointcut id="p" expression="execution(* com.ldp.controller.*Controller.*(..))"/> <!--配置切面--> <aop:aspect ref="logAop03"> <!--增強作用在具體的方法上--> <aop:before method="method01" pointcut-ref="p"/> </aop:aspect> </aop:config> </beans>
步驟三:測試
測試與之前的一樣,略。
7.AOP優化與擴展
1.抽取相同的切入點
2.同一個通知有多個切入點
3.同一個方法有多個通知
使用@Order(數字) 排序
4.使用全注解開發
刪除之前的配置文件,添加一個Aop的配置對象

@Configuration @ComponentScan(basePackages = {"com.ldp"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class AopConfig { }
測試
8.ProceedingJoinPoint 與 JoinPoint 的使用
二者的關系
作用:
1、用來獲取被增強方法的參數、方法名、權限命名、注解等,實際上能獲取到這些在結合反射可以實現很強大的功能;
2、在環繞通知時讓代理執行方法, proceedingJoinPoint.proceed();
上面的日志實際實現案例
案例一:不使用環繞通知的情況

package com.ldp.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; /** * @Copyright (C) XXXXXXXXXXX科技股份技有限公司 * @Author: lidongping * @Date: 2021-01-14 9:52 * @Description: <p> */ @Component @Aspect public class LogAop04 { /** * 抽取相同的切入點 */ @Pointcut(value = "execution(* com.ldp.controller.*Controller.*(..))") public void methodPointcut01() { } /** * 前置通知 */ @Before(value = "methodPointcut01()") public void method01(JoinPoint joinPoint) { // 獲取請求參數 Object[] args = joinPoint.getArgs(); Signature pointSignature = joinPoint.getSignature(); // 獲取包+類名 String declaringTypeName = pointSignature.getDeclaringTypeName(); // 獲取方法名 String name = pointSignature.getName(); System.out.println("參數:" + Arrays.toString(args)); System.out.println("類權限命名:" + declaringTypeName); System.out.println("方法名:" + name); } /** * 后置通知 */ @AfterReturning(returning = "result", value = "methodPointcut01()") public void method02(Object result) { System.out.println("方法執行結果:" + result); } /** * 異常通知 */ @AfterThrowing(throwing = "ex", value = "methodPointcut01()") public void method04(JoinPoint joinPoint, Exception ex) { String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("連接點方法為:" + methodName + ",參數為:" + args + ",異常為:" + ex.getMessage()); } /** * 最終通知 */ @After(value = "methodPointcut01()") public void method05() { System.out.println("method05- @After-最終通知-增強邏輯-------------"); } }
案例二:使用環繞通知的情況

package com.ldp.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import java.util.Arrays; /** * @Copyright (C) XXXXXXXXXXX科技股份技有限公司 * @Author: lidongping * @Date: 2021-01-14 9:52 * @Description: <p> */ @Component @Aspect public class LogAop04Around { /** * 環繞通知 */ @Around(value = "execution(* com.ldp.controller.*Controller.*(..))") public Object method03(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 獲取開始時間 long start = System.currentTimeMillis(); System.out.println("方法參數:" + Arrays.toString(proceedingJoinPoint.getArgs())); //讓代理方法執行 Object result = proceedingJoinPoint.proceed(); // 獲取結束執行時間 long end = System.currentTimeMillis(); System.out.println("方法返回:" + result); System.out.println("方法執行時間:" + (end - start) + " millisecond"); return result; } }
測試與上面一樣略!