本篇內容包括:Spring AOP 概述(AOP 簡介、AOP 為什么叫面向切面編程、AOP 主要用來解決的問題 和 AOP 的相關術語)、Spring AOP Demo(xml 方式、注解方式)以及相關知識點(JDK 動態代理和 CGLIB 代理、Spring AOP 和 AspectJ AOP、@Aspect、@Pointcut、@Around 注解)等內容!
一、Spring AOP 概述
1、AOP 簡介
AOP(Aspect oriented programming),即面向切面編程,它是一個編程范式,是 OOP(面向對象編程)的一種延續,目的就是提高代碼的模塊性。
Spring AOP 基于動態代理的方式實現,如果是實現了接口的話就會使用 JDK 動態代理,反之則使用 CGLIB 代理,Spring中 AOP 的應用主要體現在 事務、日志、異常處理等方面,通過在代碼的前后做一些增強處理,可以實現對業務邏輯的隔離,提高代碼的模塊化能力,同時也是解耦。Spring主要提供了 Aspect 切面、JoinPoint 連接點、PointCut 切入點、Advice 增強等實現方式。
2、AOP 為什么叫面向切面編程
切 :指的是橫切邏輯,原有業務邏輯代碼不動,只能操作橫切邏輯代碼,所以面向橫切邏輯
面 :橫切邏輯代碼往往要影響的是很多個方法,每個方法如同一個點,多個點構成一個面。這里有一個面的概念
3、AOP 主要用來解決的問題
例如:現有三個類,Horse
、Pig
、Dog
,這三個類中都有 eat 和 run 兩個方法。
通過 OOP 思想中的繼承,我們可以提取出一個 Animal 的父類,然后將 eat 和 run 方法放入父類中,Horse
、Pig
、Dog
通過繼承Animal
類即可自動獲得 eat()
和 run()
方法。這樣將會少些很多重復的代碼。
OOP 編程思想可以解決大部分的代碼重復問題。但是有一些問題是處理不了的。比如在父類 Animal 中的多個方法的相同位置出現了重復的代碼,OOP 就解決不了。這部分重復的代碼,一般統稱為橫切邏輯代碼。
橫切邏輯代碼存在的問題:
- 代碼重復問題
- 橫切邏輯代碼和業務代碼混雜在一起,代碼臃腫,不變維護
AOP 就是用來解決這些問題的:AOP 另辟蹊徑,提出橫向抽取機制,將橫切邏輯代碼和業務邏輯代碼分離,代碼拆分比較容易,難的是如何在不改變原有業務邏輯的情況下,悄無聲息的將橫向邏輯代碼應用到原有的業務邏輯中,達到和原來一樣的效果
AOP 主要用來解決:在不改變原有業務邏輯的情況下,增強橫切邏輯代碼,根本上解耦合,避免橫切邏輯代碼重復。
4、AOP 的相關術語
- 連接點(Joinpoint):所謂連接點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支持方法類型的連接點。
- 切入點(Pointcut):切入點是指我們要對哪些連接點(Joinpoint)進行攔截
- 通知/增強(Advice):所謂通知是指攔截到Joinpoint之后要做的事情,通知分為前置通知,后置通知,異常通知,最終通知,環繞通知(切面要完成的功能)。
- 織入(Weaving):是指把增強應用到目標對象來創建新的代理對象的過程。spring采用動態代理織入,而AspectJ采用編譯期織入和類裝載期織入。
- 切面(Aspect):是切入點和通知(引介)的結合
- 代理(Proxy):一個類被AOP織入增強后,就產生了一個結果代理類
- 目標對象(Target):代理的目標對象
二、Spring AOP Demo
1、xml配置方式
# 引入依賴
<!-- 模塊構建在 spring-core 和 spring-Beans 之上。它繼承了 Bean 模塊的特性,并添加了對國際化、事件傳播、資源加載透明化的支持 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.22</version></dependency>
# 配置 xml 信息
<?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:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.2.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.2.xsd"><bean id="aopTank" class="designpattern.aop.v1.AopTank"/><bean id="aopMethod" class="designpattern.aop.v1.AopMethod"/><aop:config><aop:aspect id="time" ref="aopMethod"><aop:pointcut id="onmove" expression="execution(public void com.liziheng.demo.api.aop.demo.AopDog.*(..))"/><aop:before method="before" pointcut-ref="onmove"/><aop:after method="after" pointcut-ref="onmove"/></aop:aspect></aop:config></beans>
# 切入時添加方法
public class AopMethod {public void before() {System.out.println("before...");}public void after() {System.out.println("after...");}
}
# 被切入的類
public class AopDog {public void eat() {System.out.println("The dog is eating...");}public void drink() {System.out.println("The dog is drinking water...");}}
# 測試
public class Main {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("app.xml");AopDog dog = (AopDog) context.getBean("AopDog");tank.move();tank.voice();}
}
2、注解方式
# 引入依賴
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version></dependency>
# xml配置同上
<?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:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.2.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.2.xsd"><aop:aspectj-autoproxy/><bean id="aopTank" class="designpattern.aop.v1.AopTank"/><bean id="aopMethod" class="designpattern.aop.v1.AopMethod"/>
</beans>
# 切入時添加方法
public class AopMethod {@Before("execution(public void com.liziheng.demo.api.aop.demo.AopDog.*(..))")public void before() {System.out.println("before...");}@After("execution(public void com.liziheng.demo.api.aop.demo.AopDog.*(..))")public void after() {System.out.println("after...");}
}
# 被切入的類 同xml方式
# 測試 同xml方式
三、相關知識點
1、JDK 動態代理和 CGLIB 代理
JDK 動態代理主要是針對類實現了某個接口,AOP 則會使用 JDK 動態代理。他基于反射的機制實現,生成一個實現同樣接口的一個代理類,然后通過重寫方法的方式,實現對代碼的增強;
而如果某個類沒有實現接口,AOP 則會使用 CGLIB 代理。他的底層原理是基于 asm 第三方框架,通過修改字節碼生成成成一個子類,然后重寫父類的方法,實現對代碼的增強。
2、Spring AOP 和 AspectJ AOP
Spring AOP 基于動態代理實現,屬于運行時增強。
AspectJ 則屬于編譯時增強,主要有3種方式:
- 編譯時織入:指的是增強的代碼和源代碼我們都有,直接使用 AspectJ 編譯器編譯就行了,編譯之后生成一個新的類,他也會作為一個正常的 Java 類裝載到 JVM;
- 編譯后織入:指的是代碼已經被編譯成 class 文件或者已經打成 jar 包,這時候要增強的話,就是編譯后織入,比如你依賴了第三方的類庫,又想對他增強的話,就可以通過這種方式;
- 加載時織入:指的是在 JVM 加載類的時候進行織入。
總結下來的話,就是 Spring AOP 只能在運行時織入,不需要單獨編譯,性能相比 AspectJ 編譯織入的方式慢,而 AspectJ 只支持編譯前后和類加載時織入,性能更好,功能更加強大。
3、@Aspect、@Pointcut、@Around 注解
- @Pointcut表示一個切入點,value表示切入點的作用范圍
- @Aspect 表示申明一個切面
- @Around,環繞增強:方法正常之前的前后調用
- @Before,前置增強:方法執行前調用
- @After,后置 final 增強:不管方法正常退出還是一場都會執行
- @AfterReturning,后置增強:方法正常退出時執行
- @AfterThowing,異常拋出增強:方法拋異常時執行