1. Spring的AOP簡介
1.1 什麼是AOP?
AOP 為 Aspect Oriented Programming 的縮寫, 意思為面向切面程式設計,
是通過 預編譯方式 和 執行期動態代理 實現程式功能的統一維護的一種技術。
AOP是OOP(Ojebct Oriented Programming)的延續, 是軟體開發中的一個熱點, 是Spring框架的一個重要內容部分,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
OOP的侷限性
在業務程式碼中通常有日誌控制,效能統計,安全控制,事務控制,異常處理等等操作, 儘管使用OOP可以通過封裝或繼承的方式達到程式碼的重用,但任然有同樣的程式碼分散各個方法中,採用OOP處理日誌等增強了開發者工作量,而且提高了升級維護困難
這一條條橫線彷彿切開了 OOP 的樹狀結構,猶如一個大蛋糕被切開多層,每一層都會執行相同的輔助邏輯,所以大家將這些輔助邏輯稱為層面或者切面。
1.2 AOP的優勢
作用: 在程式執行期間, 在不修改原始碼的情況下對方法進行功能加強
優勢: 減少重複程式碼, 提高開發效率,便於維護。
1.3 AOP的底層實現
實際上,AOP 的底層是通過 Spring 提供的的動態代理技術實現的。在執行期間,Spring通過動態代理技術動態 的生成代理物件,代理物件方法執行時進行增強功能的介入,在去呼叫目標物件的方法,從而完成功能的增強
1.4 AOP的代理技術
常用的動態代理技術
- JDK 代理 :基於介面的動態代理技術
- cglib 代理 :基於父類的動態代理技術
常用的動態代理技術
- JDK 代理 : 基於介面的動態代理技術
- cglib 代理:基於父類的動態代理技術
1.5 JDK的動態代理
- 目標類介面
public interface TargetInterface{
void save();
}
2. 目標類
public class Target implements TargetInterface{
public void save(){
System.out.println("save running....");
}
}
- 增加類
public class Advice{
public void before(){
System.out.println("前置增強");
}
public void afterReturning(){
System.out.println("後置增強...");
}
}
- 動態代理程式碼
public class ProxyTest{
public static void main(String [] args){
// 目標物件
final Target target = new Target();
// 增強物件
final Advice advice = new Advice();
//返回值 就是動態生成的代理物件
TargetInterface proxy = (TargetInterface)Proxy.newProxyInstance(
// 目標物件類載入器
target.getClass().getClassLoader(),
// 目標物件相同的介面位元組碼物件陣列
target.getClass().getInterfaces(),
new InvocationHandler() {
// 呼叫代理物件的任何方法 實質執行的都是invoke方法
public Object invoke(Object proxy,Method method, Object[] args) throws InvocationTargetException, IllegalAccessException{
advkce.before(); // 前置增加
Object invoke = method.invoke(target,args) //執行目標方法
advice.afterReturning(); //後置增加
return invoke; // 返回值就是目標物件 -> null也行(儘量別)
}
}
);
// 呼叫代理物件方法
proxy.save();
// (呼叫介面的任意方法時,代理物件的程式碼都無需修改)
}
}
輸出結果 :
前置增加
save running.....
後置增加
1.6 cglib的動態代理
1)目標類
public class Target {
public void save() {
System.out.println("save running.....");
}
}
2)增強類物件
public class Advice {
public void before(){
System.out.println("前置增強....");
}
public void afterReturning(){
System.out.println("後置增強....");
}
}
3)動態代理程式碼
public class ProxyTest {
public static void main(String[] args) {
// 目標物件
final Target target = new Target();
// 增加物件
final Advice advice = new Advice();
// 返回值 就是動態生成的代理物件 基於從cglib
// 1. 建立增強器
Enhancer enhancer = new Enhancer();
// 2. 設定父類(目標)
enhancer.setSuperclass(Target.class);
// 3. 設定回撥
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//執行前置
advice.before();
Object invoke = method.invoke(target, args);
// 執行後置
advice.afterReturning();
return invoke;
}
});
// 4. 建立代理物件
Target proxy = (Target) enhancer.create();
proxy.save();
}
}
1.7 AOP的相關概念
Spring 的 AOP 實現底層就是對上面的動態代理的程式碼進行了封裝,封裝後我們只需要對需要關注的部分進行程式碼編 寫,並通過配置的方式完成指定目標的方法增強。
AOP 常用的相關術語如下:
- Target(目標物件) : 代理的目標物件
- Proxy (代理) :一個類被AOP織入增強後,就會產生一個結果代理類
- Joinpoint(連線點): 所謂連線點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支援方法型別的連線點
- Pointcut(切入點):所謂的切入點是指我們要對那些 JoinPoint 進行攔截的定義
- Advice(通知/增加):所謂通知是 攔截到 Joinpoint之後所要做的是事情就是通知
- Aspect(切面):是切入點和通知(引介)的結合
- Weaving(織入):是指把增強應用到目標物件來建立新的代理物件的過程。spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入
Spring AOP術語:
連線點和切點的區別
- 連線點(Join point) : 連線點是應用執行過程中能夠插入切面(Aspect) 的一個點, 這些點可以是呼叫方法時、甚至修改一個欄位時。
- 切點(Pointcut): 切點時指通知(Adivce) 所要織入的地方的具體位置
連線點:連線點時一個虛擬
的概念,他可以理解為所有滿足切點條件的所有時機。
具體舉個例子: 比如開車經過一條高速公路,這條高速公路有很多個路口(連線點),但是我們不會每個出口都會出去,指揮選擇我們需要的那個出口(切點) 開出去。
簡單可以理解為,每個出口都是連線點
但是我們使用的那個出口才是切點
,每個應用有多個位置適合織入通知,這些都是連線點,但是隻有我們選擇的那個具體的位置才是切點
小總結: 所有方法都需要連線點,切點是我們對連線點的實現,通過切點確定那些連線點需要被處理。
參考:https://blog.csdn.net/weixin_...
AOP 開發明確開發及總結
需要編寫的內容
- 編寫目標類的目標方)
- 編寫切面類(內含有增強方法)
- 在配置檔案中,配置織入關係,以及那些
通知
和連線點
集合
總結
aop的重點概念
Pointcut(切入點):被增強的方法
Advice(通知/ 增強):封裝增強業務邏輯的方法
Aspect(切面):切點+通知
Weaving(織入):將切點與通知結合的過程
開發者步驟
誰是切點(切點表示式)
誰是通知(切面類的增強方法)
將切點和通知進行織入配置
基於XML的AOP開發
2.1快速入門
- maven AOP 的座標
- 目標介面 目標類(內部有切點)
- 切面類
- 在applicationContext.xml配置織入等
- 測試
1.導包
<dependencies>
<!-- 匯入spirng的context座標,context依賴aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- aop小框架 aspectj的織入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 這是一個spring自帶的測試等等我會演示 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
2.目標介面目標類(內部有切點)
package com.xxx.aop; // 目標介面
public interface TargetInterface {
public void save();
}
package com.xxx.aop; // 目標類
public class Target implements TargetInterface {
public void save() {
// int i = 1/0;
System.out.println("save running.....");
}
}
3.切面類(內部有增強方法)
package com.xxx.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
public void before(){
System.out.println("前置增強......");
}
}
4.xml進行配置
- aop名稱空間及約束空間
xml程式碼
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
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-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
">
<!-- 目標物件 -->
<bean id="target" class="com.xxx.aop.Target"></bean>
<!-- 切面物件 -->
<bean id="myAspect" class="com.xxx.aop.MyAspect"></bean>
<!-- 配置織入: 告訴spring框架 那些方法(切點)需要進行那些增強(前置,後置..)-->
<aop:config>
<!-- 宣告切面 -->
<aop:aspect ref="myAspect">
<!-- 配置Target的save方法執行時要進行myAspect的before方法前置增強 -->
<!--切面: 切點+通知(增加) pointcut切點 -->
<aop:before method="before" pointcut="execution(public void com.xxx.aop.Target.())"/>
</aop:aspect>
</aop:config>
</beans>
5.測試
@RunWith(SpringJUnit4ClassRunner.class) // spring測試類
@ContextConfiguration("classpath:applicationContext.xml") // 指定測試的檔案
public class AopTest {
@Autowired // 把要測試的物件 注入進來
private TargetInterface target;
@Test
public void test1() {
target.save();
}
}
輸出結果:
前置增強......
save running.....
xml配置AOP詳解
1) 切點表示式的寫法
表示式語法
execution([修飾符] 返回值型別 包名.類名.方法名(引數))
- 修飾符可以省略
- 返回值型別、包名、類名、方法名可以使用星號* 代表任意
- 包名與類名之間一個點
.
代表當前包下的類,兩個點..
表示當前包及其子包下的類 - 引數列表可以使用兩個點
..
表示任意個數,任意型別的引數列表
# 全
execution(public void com.xxx.aop.Target.method())
# 任意方法任意引數
execution(void com.xxx.aop.Target.*(..))
# 任意返回值com.xxx.aop包下任意類任意方法任意引數
execution(* com.xxx.aop.*.*(..))
# 任意返回值com.xxx.aop下以其子包下任意類任意引數
execution(* com.xxx.aop..*.*(..))
# 你故意找茬是吧
execution(* *..*.*(..))
小總結:
修飾符可以不寫
第一個 * = 返回值
第二個 * = 包
第三個 * = 類
第四個 * = 方法
括號外.. = 自己包括子包下所有類的xxx
括號內的 .. = 任意引數
2)通知的型別
通知的配置語法:
<aop:通知型別 method="切面類中方法名" pointcut="切點表示式"></aop:通知型別>
名稱 | 標籤 | 說明 |
---|---|---|
前置通知 | <aop:before> | 用於配置前置通知。指定增強的方法在切入點方法之前執行 |
後置通知 | <aop:after-returning> | 用於配置後置通知。指定增強的方法在切入點方法之後執行 |
環繞通知 | <aop:around> | 於配置環繞通知。指定增強的方法在切入點方法之前和之後都 執行 |
異常丟擲通知 | <aop:throwing> | 用用於配置異常丟擲通知。指定增強的方法在出現異常時執行 |
最終通知 | <aop:after> | 用於配置最終通知。無論增強方式執行是否有異常都會執行 |
3)切點表示式的抽取
當多個增強的切點表示式相同時,可以將切點表示式進行抽取,在增強中使用 pointcut-ref
屬性代替 pointcut
屬性來引用抽取後的切點表示式。
<aop:config>
<!--引用myAspect的Bean為切面物件-->
<aop:aspect ref="myAspect">
<!--抽取切點表示式-->
<aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"></aop:pointcut>
<aop:around method="around" pointcut-ref="myPointcut"/>
<aop:after method="after" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
通知方法存在於切面類中,根據切點表示式,對切點增強。
3. 基於註解的AOP開發
3.1 快速入門
步驟:
1、建立目標介面和目標類(開啟註解掃描)
2、建立切面類 (開啟註解掃描 配置織入關係)
3、xml開啟啟元件掃描和 AOP 的自動代理
4、測試
1、建立目標介面和目標類(內部有切點)
public interface TargetInterface {
public void save();
}
@Component
public class Target implements TargetInterface {
public void save() {
System.out.println("save running.....");
}
}
2、建立切面類(內部有增強方法)
@Component("myAspect")
@Aspect //標籤當前MyAspect是一個切面類
public class MyAspect {
// 配置前置增強
// @Before(value = "execution( * com.xxx.anno.*.*(..))")
public void before(){
System.out.println("前置增強......");
}
}
5、在配置檔案中開啟元件掃描和 AOP 的自動代理
<!--元件掃描-->
<context:component-scan base-package="com.itheima.anno"/>
<!--aop自動代理-->
<aop:aspectj-autoproxy/>
6、測試
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
public class AnnoTest {
@Autowired
private TargetInterface target;
@Test
public void test1() {
target.save();
}
}
輸出:
前置增強......
save running.....
3.2 註解配置 AOP 詳解
1)註解通知的型別
通知的配置語法:@通知註解("切點表示式")
名稱 | 註解 | 說明 |
---|---|---|
前置通知 | @Before | 用於配置前置通知。指定增強的方法在切入點方法之前執行 |
後置通知 | @AfterReturning | 用於配置後置通知。指定增強的方法在切入點方法之後執行 |
環繞通知 | @Around | 用於配置環繞通知。指定增強的方法在切入點方法之前和之後都執行 |
異常丟擲通知 | @AfterThrowing | 用於配置異常丟擲通知。指定增強的方法在出現異常時執行 |
最終通知 | @After | 用於配置最終通知。無論增強方式執行是否有異常都會執行 |
2)切點表示式的抽取
通xml 配置 aop 一樣,我們可以將切點表示式抽取。抽取方式是在切面內定義方法,在該方法上使用@Pointcut
註解定義切點表示式,然後在在增強註解中進行引用。具體如下:
@Component("myAspect")
@Aspect //標註當前MyAspect是一個切面類
public class MyAspect {
//Proceeding JoinPoint: 正在執行的連線點===切點
//@Around("execution(* com.xxx.anno.*.*(..))")
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("環繞前增強....");
Object proceed = pjp.proceed();//切點方法
System.out.println("環繞後增強....");
return proceed;
}
public void afterThrowing() {
System.out.println("異常丟擲增強..........");
}
//@After("execution(* com.xxx.anno.*.*(..))")
@After("MyAspect.pointcut()")
public void after() {
System.out.println("最終增強..........");
}
//定義切點表示式
@Pointcut("execution(* com.xxx.anno.*.*(..))")
public void pointcut() {
}
}