AOP
概念
- 什么是 AOP
- 面向切面編程(方面),利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
- 通俗描述:不通過修改源代碼方式,在主干功能里面添加新功能
- 使用登錄例子說明 AOP
底層原理
- AOP 底層使用動態代理
有兩種情況動態代理第一種 有接口情況,使用 JDK 動態代理
創建接口實現類代理對象,增強類的方法
第二種 沒有接口情況,使用 CGLIB 動態代理
創建子類的代理對象,增強類的方法
JDK 動態代理
- 使用 JDK 動態代理,使用 Proxy 類里面的方法創建代理對象
- 調用 newProxyInstance 方法
方法有三個參數:- 第一參數,類加載器
- 第二參數,增強方法所在的類,這個類實現的接口,支持多個接口
- 第三參數,實現這個接口 InvocationHandler,創建代理對象,寫增強的部分
- 調用 newProxyInstance 方法
- 編寫 JDK 動態代理代碼
創建接口,定義方法
public interface UserDao { public int add(int a,int b); public String update(String id); }
創建接口實現類,實現方法
public class UserDaoImpl implements UserDao { @Override public int add(int a, int b) { return a+b; } @Override public String update(String id) { return id; } }
使用 Proxy 類創建接口代理對象
public class JDKProxy { public static void main(String[] args) { //創建接口實現類代理對象 Class[] interfaces = {UserDao.class}; // Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() { // @Override // public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // return null; // } // }); UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = dao.add(1, 2); System.out.println("result:"+result); } }
//創建代理對象代碼 class UserDaoProxy implements InvocationHandler { //1 把創建的是誰的代理對象,把誰傳遞過來 //有參數構造傳遞 private Object obj; public UserDaoProxy(Object obj) { this.obj = obj; } //增強的邏輯 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println("方法之前執行...."+method.getName()+" :傳遞的參數..."+ Arrays.toString(args)); //被增強的方法執行 Object res = method.invoke(obj, args); //方法之后 System.out.println("方法之后執行...."+obj); return res; } }
術語
- 連接點:類里面哪些方法可以被增強,這些方法稱為連接點
- 切入點:實際被真正增強的方法,稱為切入點
- 通知(增強):
- 實際增強的邏輯部分稱為通知(增強)
- 通知有多種類型
- 前置通知
- 后置通知
- 環繞通知
- 異常通知
- 最終通知
- 切面:是動作,把通知應用到切入點的過程
AOP 操作
准備工作
- Spring 框架一般都是基於 AspectJ 實現 AOP 操作
- AspectJ 不是 Spring 組成部分,獨立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,進行 AOP 操作
- 基於 AspectJ 實現 AOP 操作
- 基於 xml 配置文件實現
- 基於注解方式實現(使用)
- 在項目工程里面引入 AOP 相關依賴
- 切入點表達式
- 切入點表達式作用:知道對哪個類里面的哪個方法進行增強
語法結構: execution([權限修飾符] [返回類型] [類全路徑] 方法名稱)
舉例 1:對 com.nemo.dao.BookDao 類里面的 add 進行增強
execution(* com.nemo.dao.BookDao.add(..))
舉例 2:對 com.nemo.dao.BookDao 類里面的所有的方法進行增強
execution(* com.nemo.dao.BookDao.* (..))
舉例 3:對 com.nemo.dao 包里面所有類,類里面所有方法進行增強
execution(* com.nemo.dao.. (..))
AspectJ 注解
創建類,在類里面定義方法
public class User { public void add() { System.out.println("add......."); } }
- 創建增強類(編寫增強邏輯)
在增強類里面,創建方法,讓不同方法代表不同通知類型
//增強的類 public class UserProxy { public void before() {//前置通知 System.out.println("before......"); } }
- 進行通知的配置
在 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" 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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 開啟注解掃描 --> <context:component-scan basepackage="com.nemo.spring5.aopanno"></context:component-scan>
- 使用注解創建 User 和 UserProxy 對象
在增強類上面添加注解 @Aspect
//增強的類 @Component @Aspect //生成代理對象 public class UserProxy {
在 spring 配置文件中開啟生成代理對象
<!-- 開啟 Aspect 生成代理對象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 配置不同類型的通知
在增強類的里面,在作為通知方法上面添加通知類型注解,使用切入點表達式配置
//增強的類 @Component @Aspect //生成代理對象 public class UserProxy { //前置通知 //@Before 注解表示作為前置通知 @Before(value = "execution(* com.nemo.spring5.aopanno.User.add(..))") public void before() { System.out.println("before........."); } //后置通知(返回通知) @AfterReturning(value = "execution(* com.nemo.spring5.aopanno.User.add(..))") public void afterReturning() { System.out.println("afterReturning........."); } //最終通知 @After(value = "execution(* com.nemo.spring5.aopanno.User.add(..))") public void after() { System.out.println("after........."); } //異常通知 @AfterThrowing(value = "execution(* com.nemo.spring5.aopanno.User.add(..))") public void afterThrowing() { System.out.println("afterThrowing........."); } //環繞通知 @Around(value = "execution(* com.nemo.spring5.aopanno.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("環繞之前........."); //被增強的方法執行 proceedingJoinPoint.proceed(); System.out.println("環繞之后........."); } }
相同的切入點抽取
//相同切入點抽取 @Pointcut(value = "execution(* com.nemo.spring5.aopanno.User.add(..))") public void pointdemo() { } //前置通知 //@Before 注解表示作為前置通知 @Before(value = "pointdemo()") public void before() { System.out.println("before........."); }
- 有多個增強類多同一個方法進行增強,設置增強類優先級
在增強類上面添加注解 @Order(數字類型值),數字類型值越小優先級越高
@Component @Aspect @Order(1) public class PersonProxy
- 完全使用注解開發
創建配置類,不需要創建 xml 配置文件
@Configuration @ComponentScan(basePackages = {"com.nemo"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class ConfigAop { }
AspectJ 配置文件
- 創建兩個類,增強類和被增強類,創建方法
在 spring 配置文件中創建兩個類對象
<!--創建對象--> <bean id="book" class="com.nemo.spring5.aopxml.Book"></bean> <bean id="bookProxy" class="com.nemo.spring5.aopxml.BookProxy"></bean>
3、在 spring 配置文件中配置切入點
<!--配置 aop 增強--> <aop:config> <!--切入點--> <aop:pointcut id="p" expression="execution(* com.nemo.spring5.aopxml.Book.buy(..))"/> <!--配置切面--> <aop:aspect ref="bookProxy"> <!--增強作用在具體的方法上--> <aop:before method="before" pointcut-ref="p"/> </aop:aspect> </aop:config>