東莞網(wǎng)站建設(shè)公司 網(wǎng)絡(luò)服務(wù)杭州推廣公司
spring5(五):AOP操作
- 前言
- 一、代理模式
- 1、場(chǎng)景模擬
- 2、代理模式
- 2.1 概念
- 2.2 靜態(tài)代理
- 2.3 動(dòng)態(tài)代理
- 二、AOP概述
- 1、什么是 AOP?
- 2、相關(guān)術(shù)語
- 3、作用
- 三、AOP底層原理
- 1、AOP 底層使用動(dòng)態(tài)代理
- 2、AOP(JDK 動(dòng)態(tài)代理)
- 2.1 編寫 JDK 動(dòng)態(tài)代理代碼
- 四、AOP 操作的準(zhǔn)備工作
- 1、AspectJ概述
- 2、依賴的引入
- 五、AOP操作(注解版)
- 1、添加依賴
- 2、準(zhǔn)備被代理的目標(biāo)資源
- 3、在Spring的配置文件中配置
- 4、創(chuàng)建切面類并配置
- 5、測(cè)試
- 六、重點(diǎn)
- 1、 切入點(diǎn)表達(dá)式
- 2、重用切入點(diǎn)表達(dá)式
- 3、獲取通知的相關(guān)信息
- 4、環(huán)繞通知
- 5、切面的優(yōu)先級(jí)
- 6、完全使用注解開發(fā)
- 七、AOP 操作(AspectJ 配置文件)
- 1、創(chuàng)建兩個(gè)類,增強(qiáng)類和被增強(qiáng)類,創(chuàng)建方法
- 2、在 spring 配置文件中創(chuàng)建兩個(gè)類對(duì)象
- 3、在 spring 配置文件中配置切入點(diǎn)
前言
本博主將用CSDN記錄軟件開發(fā)求學(xué)之路上親身所得與所學(xué)的心得與知識(shí),有興趣的小伙伴可以關(guān)注博主!也許一個(gè)人獨(dú)行,可以走的很快,但是一群人結(jié)伴而行,才能走的更遠(yuǎn)!讓我們?cè)诔砷L(zhǎng)的道路上互相學(xué)習(xí),歡迎關(guān)注!
一、代理模式
1、場(chǎng)景模擬
(1)聲明計(jì)算器接口Calculator,包含加減乘除的抽象方法
public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}
(2)創(chuàng)建實(shí)現(xiàn)類
public class CalculatorPureImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("方法內(nèi)部 result = " + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("方法內(nèi)部 result = " + result);return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("方法內(nèi)部 result = " + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("方法內(nèi)部 result = " + result);return result;}
}
(3)創(chuàng)建帶日志功能的實(shí)現(xiàn)類
public class CalculatorLogImpl implements Calculator {@Overridepublic int add(int i, int j) {System.out.println("[日志] add 方法開始了,參數(shù)是:" + i + "," + j);int result = i + j;System.out.println("方法內(nèi)部 result = " + result);System.out.println("[日志] add 方法結(jié)束了,結(jié)果是:" + result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("[日志] sub 方法開始了,參數(shù)是:" + i + "," + j);int result = i - j;System.out.println("方法內(nèi)部 result = " + result);System.out.println("[日志] sub 方法結(jié)束了,結(jié)果是:" + result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("[日志] mul 方法開始了,參數(shù)是:" + i + "," + j);int result = i * j;System.out.println("方法內(nèi)部 result = " + result);System.out.println("[日志] mul 方法結(jié)束了,結(jié)果是:" + result);return result;}@Overridepublic int div(int i, int j) {System.out.println("[日志] div 方法開始了,參數(shù)是:" + i + "," + j);int result = i / j;System.out.println("方法內(nèi)部 result = " + result);System.out.println("[日志] div 方法結(jié)束了,結(jié)果是:" + result);return result;}
}
(4)提出問題
? 現(xiàn)有代碼缺陷:
針對(duì)帶日志功能的實(shí)現(xiàn)類,我們發(fā)現(xiàn)有如下缺陷: 對(duì)核心業(yè)務(wù)功能有干擾,導(dǎo)致程序員在開發(fā)核心業(yè)務(wù)功能時(shí)分散了精力
附加功能分散在各個(gè)業(yè)務(wù)功能方法中,不利于統(tǒng)一維護(hù)
? 解決思路:
解決這兩個(gè)問題,核心就是:解耦。我們需要把附加功能從業(yè)務(wù)功能代碼中抽取出來。
? 困難:
解決問題的困難:要抽取的代碼在方法內(nèi)部,靠以前把子類中的重復(fù)代碼抽取到父類的方式?jīng)]法解決。 所以需要引入新的技術(shù)。
2、代理模式
2.1 概念
①介紹
二十三種設(shè)計(jì)模式中的一種,屬于結(jié)構(gòu)型模式。它的作用就是通過提供一個(gè)代理類,讓我們?cè)谡{(diào)用目標(biāo)方法的時(shí)候,不再是直接對(duì)目標(biāo)方法進(jìn)行調(diào)用,而是通過代理類間接調(diào)用。讓不屬于目標(biāo)方法核心邏輯的代碼從目標(biāo)方法中剝離出來——解耦。調(diào)用目標(biāo)方法時(shí)先調(diào)用代理對(duì)象的方法,減少對(duì)目標(biāo)方法的調(diào)用和打擾,同時(shí)讓附加功能能夠集中在一起也有利于統(tǒng)一維護(hù)。
? 使用代理前:
? 使用代理后:
②生活中的代理
● 廣告商找大明星拍廣告需要經(jīng)過經(jīng)紀(jì)人
● 合作伙伴找大老板談合作要約見面時(shí)間需要經(jīng)過秘書
● 房產(chǎn)中介是買賣雙方的代理
③相關(guān)術(shù)語
● 代理:將非核心邏輯剝離出來以后,封裝這些非核心邏輯的類、對(duì)象、方法。
● 目標(biāo):被代理“套用”了非核心邏輯代碼的類、對(duì)象、方法。
2.2 靜態(tài)代理
public class CalculatorStaticProxy implements Calculator {// 將被代理的目標(biāo)對(duì)象聲明為成員變量private Calculator target;public CalculatorStaticProxy(Calculator target) {this.target = target;}@Overridepublic int add(int i, int j) {// 附加功能由代理類中的代理方法來實(shí)現(xiàn)System.out.println("[日志] add 方法開始了,參數(shù)是:" + i + "," + j);// 通過目標(biāo)對(duì)象來實(shí)現(xiàn)核心業(yè)務(wù)邏輯int addResult = target.add(i, j);System.out.println("[日志] add 方法結(jié)束了,結(jié)果是:" + addResult);return addResult;}
}
靜態(tài)代理確實(shí)實(shí)現(xiàn)了解耦,但是由于代碼都寫死了,完全不具備任何的靈活性。就拿日志功能來
說,將來其他地方也需要附加日志,那還得再聲明更多個(gè)靜態(tài)代理類,那就產(chǎn)生了大量重復(fù)的代
碼,日志功能還是分散的,沒有統(tǒng)一管理。
提出進(jìn)一步的需求:將日志功能集中到一個(gè)代理類中,將來有任何日志需求,都通過這一個(gè)代理
類來實(shí)現(xiàn)。這就需要使用動(dòng)態(tài)代理技術(shù)了
2.3 動(dòng)態(tài)代理
生產(chǎn)代理對(duì)象的工廠類:
public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target = target;}public Object getProxy(){/*** newProxyInstance():創(chuàng)建一個(gè)代理實(shí)例* 其中有三個(gè)參數(shù):* 1、classLoader:加載動(dòng)態(tài)生成的代理類的類加載器* 2、interfaces:目標(biāo)對(duì)象實(shí)現(xiàn)的所有接口的class對(duì)象所組成的數(shù)組* 3、invocationHandler:設(shè)置代理對(duì)象實(shí)現(xiàn)目標(biāo)對(duì)象方法的過程,即代理類中如何重寫接口中的抽象方法*/ClassLoader classLoader = target.getClass().getClassLoader();Class<?>[] interfaces = target.getClass().getInterfaces();InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*** proxy:代理對(duì)象* method:代理對(duì)象需要實(shí)現(xiàn)的方法,即其中需要重寫的方法* args:method所對(duì)應(yīng)方法的參數(shù)*/Object result = null;try {System.out.println("[動(dòng)態(tài)代理][日志] "+method.getName()+",參數(shù):"+ Arrays.toString(args));result = method.invoke(target, args);System.out.println("[動(dòng)態(tài)代理][日志] "+method.getName()+",結(jié)果:"+ result);} catch (Exception e) {e.printStackTrace();System.out.println("[動(dòng)態(tài)代理][日志] "+method.getName()+",異常:"+e.getMessage());} finally {System.out.println("[動(dòng)態(tài)代理][日志] "+method.getName()+",方法執(zhí)行完畢");}return result;}};return Proxy.newProxyInstance(classLoader, interfaces,invocationHandler);}
}
測(cè)試
@Test
public void testDynamicProxy(){ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());Calculator proxy = (Calculator) factory.getProxy();proxy.div(1,0);//proxy.div(1,1);
}
二、AOP概述
1、什么是 AOP?
?
AOP(Aspect Oriented Programming)
是一種設(shè)計(jì)思想,是軟件設(shè)計(jì)領(lǐng)域中的面向切面編程,它是面向?qū)ο缶幊痰囊环N補(bǔ)充和完善,它以通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理方式實(shí)現(xiàn)在不修改源代碼的情況下給程序動(dòng)態(tài)統(tǒng)一添加額外功能的一種技術(shù)。
? 通俗描述:不通過修改源代碼方式,在主干功能里面添加新功能
2、相關(guān)術(shù)語
? 橫切關(guān)注點(diǎn)
從每個(gè)方法中抽取出來的同一類非核心業(yè)務(wù)。在同一個(gè)項(xiàng)目中,我們可以使用多個(gè)橫切關(guān)注點(diǎn)對(duì)相關(guān)方法進(jìn)行多個(gè)不同方面的增強(qiáng)。
這個(gè)概念不是語法層面天然存在的,而是根據(jù)附加功能的邏輯上的需要:有十個(gè)附加功能,就有十個(gè)橫切關(guān)注點(diǎn),比如上例中CalculatorImpl
類中的日志代碼是非核心代碼,我們把這些非核心代碼抽取出來,作為一些附加功能,即橫切關(guān)注點(diǎn)。
附加功能相對(duì)于目標(biāo)類來說是橫切關(guān)注點(diǎn),相對(duì)于目標(biāo)類來說是通知。
? 通知
每一個(gè)橫切關(guān)注點(diǎn)上要做的事情都需要寫一個(gè)方法來實(shí)現(xiàn),這樣的方法就叫通知方法。
- 前置通知:在被代理的目標(biāo)方法前執(zhí)行
- 返回通知:在被代理的目標(biāo)方法成功結(jié)束后執(zhí)行(壽終正寢)
- 異常通知:在被代理的目標(biāo)方法異常結(jié)束后執(zhí)行(死于非命)
- 后置通知:在被代理的目標(biāo)方法最終結(jié)束后執(zhí)行(蓋棺定論)
- 環(huán)繞通知:使用try…catch…finally結(jié)構(gòu)圍繞整個(gè)被代理的目標(biāo)方法,包括上面四種通知對(duì)應(yīng)的所有位置
? 切面
封裝通知方法的類。
?目標(biāo)
被代理的目標(biāo)對(duì)象。
?代理
向目標(biāo)對(duì)象應(yīng)用通知之后創(chuàng)建的代理對(duì)象。
?連接點(diǎn)
這也是一個(gè)純邏輯概念,不是語法定義的。
把方法排成一排,每一個(gè)橫切位置看成x軸方向,把方法從上到下執(zhí)行的順序看成y軸,x軸和y軸的交叉點(diǎn)就是連接點(diǎn)。
?切入點(diǎn)
定位連接點(diǎn)的方式。
每個(gè)類的方法中都包含多個(gè)連接點(diǎn),所以連接點(diǎn)是類中客觀存在的事物(從邏輯上來說)。
如果把連接點(diǎn)看作數(shù)據(jù)庫(kù)中的記錄,那么切入點(diǎn)就是查詢記錄的SQL
語句。
Spring
的AOP
技術(shù)可以通過切入點(diǎn)定位到特定的連接點(diǎn)。
切點(diǎn)通過org.springframework.aop.Pointcut
接口進(jìn)行描述,它使用類和方法作為連接點(diǎn)的查詢條件。
3、作用
- 簡(jiǎn)化代碼:把方法中固定位置的重復(fù)的代碼抽取出來,讓被抽取的方法更專注于自己的核心功能,提高內(nèi)聚性。
- 代碼增強(qiáng):把特定的功能封裝到切面類中,看哪里有需要,就往上套,被套用了切面邏輯的方法就被切面給增強(qiáng)了。
三、AOP底層原理
1、AOP 底層使用動(dòng)態(tài)代理
有兩種情況動(dòng)態(tài)代理:
? 第一種 有接口情況,使用
JDK
動(dòng)態(tài)代理創(chuàng)建接口實(shí)現(xiàn)類代理對(duì)象,增強(qiáng)類的方法
? 第二種 沒有接口情況,使用
cglib
動(dòng)態(tài)代理
創(chuàng)建子類的代理對(duì)象,增強(qiáng)類的方法
2、AOP(JDK 動(dòng)態(tài)代理)
① 使用
JDK
動(dòng)態(tài)代理,使用Proxy
類里面的方法創(chuàng)建代理對(duì)象
② 調(diào)用
newProxyInstance()
方法
方法有三個(gè)參數(shù):
- 第一參數(shù),類加載器
- 第二參數(shù),增強(qiáng)方法所在的類,這個(gè)類實(shí)現(xiàn)的接口,支持多個(gè)接口
- 第三參數(shù),實(shí)現(xiàn)這個(gè)接口InvocationHandler,創(chuàng)建代理對(duì)象,寫增強(qiáng)的部分
2.1 編寫 JDK 動(dòng)態(tài)代理代碼
① 創(chuàng)建接口,定義方法
public interface UserDao {public int add(int a,int b);public String update(String id);
}
② 創(chuàng)建接口實(shí)現(xiàn)類,實(shí)現(xiàn)方法
public class UserDaoImpl implements UserDao {@Overridepublic int add(int a, int b) {System.out.println("add方法執(zhí)行了.....");return a+b;}@Overridepublic String update(String id) {System.out.println("update方法執(zhí)行了.....");return id;}
}
③ 使用 Proxy 類創(chuàng)建接口代理對(duì)象
public class JDKProxy {public static void main(String[] args) {//創(chuàng)建接口實(shí)現(xiàn)類代理對(duì)象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);}
}//創(chuàng)建代理對(duì)象代碼
class UserDaoProxy implements InvocationHandler {//1 把創(chuàng)建的是誰的代理對(duì)象,把誰傳遞過來//有參數(shù)構(gòu)造傳遞private Object obj;public UserDaoProxy(Object obj) {this.obj = obj;}//增強(qiáng)的邏輯@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//方法之前System.out.println("方法之前執(zhí)行...."+method.getName()+" :傳遞的參數(shù)..."+ Arrays.toString(args));//被增強(qiáng)的方法執(zhí)行Object res = method.invoke(obj, args);//方法之后System.out.println("方法之后執(zhí)行...."+obj);return res;}
}
四、AOP 操作的準(zhǔn)備工作
1、AspectJ概述
?
Spring
框架一般都是基于AspectJ
實(shí)現(xiàn)AOP
操作
AspectJ
不是Spring
組成部分,獨(dú)立AOP
框架,一般把AspectJ
和Spirng
框架一起使用,進(jìn)行AOP
操作
? 基于
AspectJ
實(shí)現(xiàn)AOP
操作
- 基于
xml
配置文件實(shí)現(xiàn)- 基于注解方式實(shí)現(xiàn)
? 在項(xiàng)目工程里面引入 AOP 相關(guān)依賴
2、依賴的引入
五、AOP操作(注解版)
1、添加依賴
在
IOC
所需依賴基礎(chǔ)上再加入下面依賴即可:
<!-- spring-aspects會(huì)幫我們傳遞過來aspectjweaver -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.1</version>
</dependency>
2、準(zhǔn)備被代理的目標(biāo)資源
接口:
public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}
實(shí)現(xiàn)類:
@Component
public class CalculatorPureImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("方法內(nèi)部 result = " + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("方法內(nèi)部 result = " + result);return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("方法內(nèi)部 result = " + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("方法內(nèi)部 result = " + result);return result;}
}
3、在Spring的配置文件中配置
<!--
基于注解的AOP的實(shí)現(xiàn):
1、將目標(biāo)對(duì)象和切面交給IOC容器管理(注解+掃描)
2、開啟AspectJ的自動(dòng)代理,為目標(biāo)對(duì)象自動(dòng)生成代理
3、將切面類通過注解@Aspect標(biāo)識(shí)
-->
<context:component-scan base-package="com.atguigu.aop.annotation">
</context:component-scan>
<aop:aspectj-autoproxy />
4、創(chuàng)建切面類并配置
package com.reds.AOPTest;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;/*** Date:2022/7/4* Author:ybc* Description:* 1、在切面中,需要通過指定的注解將方法標(biāo)識(shí)為通知方法* @Before:前置通知,在目標(biāo)對(duì)象方法執(zhí)行之前執(zhí)行* @After:后置通知,在目標(biāo)對(duì)象方法的finally字句中執(zhí)行* @AfterReturning:返回通知,在目標(biāo)對(duì)象方法返回值之后執(zhí)行* @AfterThrowing:異常通知,在目標(biāo)對(duì)象方法的catch字句中執(zhí)行*** 2、切入點(diǎn)表達(dá)式:設(shè)置在標(biāo)識(shí)通知的注解的value屬性中* execution(public int com.atguigu.spring.aop.annotation.CalculatorImpl.add(int, int)* execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..)* 第一個(gè)*表示任意的訪問修飾符和返回值類型* 第二個(gè)*表示類中任意的方法* ..表示任意的參數(shù)列表* 類的地方也可以使用*,表示包下所有的類* 3、重用切入點(diǎn)表達(dá)式* //@Pointcut聲明一個(gè)公共的切入點(diǎn)表達(dá)式* @Pointcut("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")* public void pointCut(){}* 使用方式:@Before("pointCut()")** 4、獲取連接點(diǎn)的信息* 在通知方法的參數(shù)位置,設(shè)置JoinPoint類型的參數(shù),就可以獲取連接點(diǎn)所對(duì)應(yīng)方法的信息* //獲取連接點(diǎn)所對(duì)應(yīng)方法的簽名信息* Signature signature = joinPoint.getSignature();* //獲取連接點(diǎn)所對(duì)應(yīng)方法的參數(shù)* Object[] args = joinPoint.getArgs();** 5、切面的優(yōu)先級(jí)* 可以通過@Order注解的value屬性設(shè)置優(yōu)先級(jí),默認(rèn)值Integer的最大值* @Order注解的value屬性值越小,優(yōu)先級(jí)越高**/
@Component
@Aspect //將當(dāng)前組件標(biāo)識(shí)為切面
public class LoggerAspect {@Pointcut("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")public void pointCut(){}//@Before("execution(public int com.atguigu.spring.aop.annotation.CalculatorImpl.add(int, int))")//@Before("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")@Before("pointCut()")public void beforeAdviceMethod(JoinPoint joinPoint) {//獲取連接點(diǎn)所對(duì)應(yīng)方法的簽名信息Signature signature = joinPoint.getSignature();//獲取連接點(diǎn)所對(duì)應(yīng)方法的參數(shù)Object[] args = joinPoint.getArgs();System.out.println("LoggerAspect,方法:"+signature.getName()+",參數(shù):"+ Arrays.toString(args));}@After("pointCut()")public void afterAdviceMethod(JoinPoint joinPoint){//獲取連接點(diǎn)所對(duì)應(yīng)方法的簽名信息Signature signature = joinPoint.getSignature();System.out.println("LoggerAspect,方法:"+signature.getName()+",執(zhí)行完畢");}/*** 在返回通知中若要獲取目標(biāo)對(duì)象方法的返回值* 只需要通過@AfterReturning注解的returning屬性* 就可以將通知方法的某個(gè)參數(shù)指定為接收目標(biāo)對(duì)象方法的返回值的參數(shù)*/@AfterReturning(value = "pointCut()", returning = "result")public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){//獲取連接點(diǎn)所對(duì)應(yīng)方法的簽名信息Signature signature = joinPoint.getSignature();System.out.println("LoggerAspect,方法:"+signature.getName()+",結(jié)果:"+result);}/*** 在異常通知中若要獲取目標(biāo)對(duì)象方法的異常* 只需要通過AfterThrowing注解的throwing屬性* 就可以將通知方法的某個(gè)參數(shù)指定為接收目標(biāo)對(duì)象方法出現(xiàn)的異常的參數(shù)*/@AfterThrowing(value = "pointCut()", throwing = "ex")public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex){//獲取連接點(diǎn)所對(duì)應(yīng)方法的簽名信息Signature signature = joinPoint.getSignature();System.out.println("LoggerAspect,方法:"+signature.getName()+",異常:"+ex);}@Around("pointCut()")//環(huán)繞通知的方法的返回值一定要和目標(biāo)對(duì)象方法的返回值一致public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){Object result = null;try {System.out.println("環(huán)繞通知-->前置通知");//表示目標(biāo)對(duì)象方法的執(zhí)行result = joinPoint.proceed();System.out.println("環(huán)繞通知-->返回通知");} catch (Throwable throwable) {throwable.printStackTrace();System.out.println("環(huán)繞通知-->異常通知");} finally {System.out.println("環(huán)繞通知-->后置通知");}return result;}}
5、測(cè)試
public class AOPByAnnotationTest {@Test
public void testAOPByAnnotation(){ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-annotation.xml");//Calculator :代理對(duì)象和目標(biāo)對(duì)象共同實(shí)現(xiàn)的接口Calculator calculator = ioc.getBean(Calculator.class);calculator.div(10, 1);
}
如果直接獲取目標(biāo)類,此時(shí)會(huì)報(bào)無法找到實(shí)例的錯(cuò)誤。
原因是:
我們?yōu)槟繕?biāo)對(duì)象創(chuàng)建了代理對(duì)象,因此我們每次想訪問目標(biāo)對(duì)象中的方法時(shí)都不能直接通過目標(biāo)對(duì)象去訪問,必須通過代理對(duì)象來間接訪問目標(biāo)對(duì)象,進(jìn)而去訪問目標(biāo)對(duì)象中的方法,所以我們下一步應(yīng)該是去獲取代理對(duì)象,可是我們并不知道代理對(duì)象是什么,但是我們知道代理對(duì)象跟我們的目標(biāo)對(duì)象實(shí)現(xiàn)的是相同的接口,我們之前通過IOC獲取某個(gè)bean的時(shí)候,我們不需要通過某個(gè)具體的類型來獲取,我們可以用它所繼承的父類或者所實(shí)現(xiàn)的接口,都可以獲取到bean。
六、重點(diǎn)
1、 切入點(diǎn)表達(dá)式
作用
知道對(duì)哪個(gè)類里面的哪個(gè)方法進(jìn)行增強(qiáng)
語法結(jié)構(gòu)
execution([權(quán)限修飾符] [返回類型] [類全路徑] [方法名稱]([參數(shù)列表]) )
為了方便,權(quán)限修飾符一般使用
*
代替 ,返回類型一般省略不寫
? 用
*
號(hào)代替“權(quán)限修飾符”和“返回值”部分表示“權(quán)限修飾符”和“返回值”不限在包名的部分,一個(gè)“*”號(hào)只能代表包的層次結(jié)構(gòu)中的一層,表示這一層是任意的。
例如:*.Hello
匹配com.Hello
,不匹配com.atguigu.Hello
? 在包名的部分,使用“
*..
”表示包名任意、包的層次深度任意
? 在類名的部分,類名部分整體用
*
號(hào)代替,表示類名任意
? 在類名的部分,可以使用
*
號(hào)代替類名的一部分
例如:*Service
匹配所有名稱以Service
結(jié)尾的類或接口
? 在方法名部分,可以使用
*
號(hào)表示方法名任意
? 在方法名部分,可以使用
*
號(hào)代替方法名的一部分
例如:*Operation
匹配所有方法名以Operation
結(jié)尾的方法
? 在方法參數(shù)列表部分,使用
(..)
表示參數(shù)列表任意
? 在方法參數(shù)列表部分,使用
(int,..)
表示參數(shù)列表以一個(gè)int
類型的參數(shù)開頭
? 在方法參數(shù)列表部分,基本數(shù)據(jù)類型和對(duì)應(yīng)的包裝類型是不一樣的 切入點(diǎn)表達(dá)式中使用
int
和實(shí)際方法中Integer
是不匹配的
? 在方法返回值部分,如果想要明確指定一個(gè)返回值類型,那么必須同時(shí)寫明權(quán)限修飾符
例如:execution(public int ..Service.*(.., int))
正確
例如:execution(* int ..Service.*(.., int))
錯(cuò)誤
?
舉例 1:對(duì) com.atguigu.dao.BookDao 類里面的 add 進(jìn)行增強(qiáng)
execution(*com.atguigu.dao.BookDao.add(..))
舉例 2:對(duì) com.atguigu.dao.BookDao類里面的所有的方法進(jìn)行增強(qiáng)execution(* com.atguigu.dao.BookDao.* (..))
舉例 3:對(duì)com.atguigu.dao 包里面所有類,類里面所有方法進(jìn)行增強(qiáng)execution(* com.atguigu.dao.*.* (..))
2、重用切入點(diǎn)表達(dá)式
①聲明
@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){}
②在同一個(gè)切面中使用
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());System.out.println("Logger-->前置通知,方法名:"+methodName+",參數(shù):"+args);
}
③在不同切面中使用
@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());System.out.println("Logger-->前置通知,方法名:"+methodName+",參數(shù):"+args);
}
3、獲取通知的相關(guān)信息
①獲取連接點(diǎn)信息
獲取連接點(diǎn)信息可以在通知方法的參數(shù)位置設(shè)置
JoinPoint
類型的形參
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){//獲取連接點(diǎn)的簽名信息String methodName = joinPoint.getSignature().getName();//獲取目標(biāo)方法到的實(shí)參信息String args = Arrays.toString(joinPoint.getArgs());System.out.println("Logger-->前置通知,方法名:"+methodName+",參數(shù):"+args);
}
②獲取目標(biāo)方法的返回值
@AfterReturning
中的屬性returning
,用來將通知方法的某個(gè)形參,接收目標(biāo)方法的返回值
@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){String methodName = joinPoint.getSignature().getName();System.out.println("Logger-->返回通知,方法名:"+methodName+",結(jié)果:"+result);
}
③獲取目標(biāo)方法的異常
@AfterThrowing
中的屬性throwing
,用來將通知方法的某個(gè)形參,接收目標(biāo)方法的異常
@AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){String methodName = joinPoint.getSignature().getName();System.out.println("Logger-->異常通知,方法名:"+methodName+",異常:"+ex);
}
4、環(huán)繞通知
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());Object result = null;try {System.out.println("環(huán)繞通知-->目標(biāo)對(duì)象方法執(zhí)行之前");//目標(biāo)方法的執(zhí)行,目標(biāo)方法的返回值一定要返回給外界調(diào)用者result = joinPoint.proceed();System.out.println("環(huán)繞通知-->目標(biāo)對(duì)象方法返回值之后");} catch (Throwable throwable) {throwable.printStackTrace();System.out.println("環(huán)繞通知-->目標(biāo)對(duì)象方法出現(xiàn)異常時(shí)");} finally {System.out.println("環(huán)繞通知-->目標(biāo)對(duì)象方法執(zhí)行完畢");}return result;
}
5、切面的優(yōu)先級(jí)
? 相同目標(biāo)方法上同時(shí)存在多個(gè)切面時(shí),切面的優(yōu)先級(jí)控制切面的內(nèi)外嵌套順序。
- 優(yōu)先級(jí)高的切面:外面
- 優(yōu)先級(jí)低的切面:里面
? 使用
@Order
注解可以控制切面的優(yōu)先級(jí):
@Order
(較小的數(shù)):優(yōu)先級(jí)高@Order
(較大的數(shù)):優(yōu)先級(jí)低
6、完全使用注解開發(fā)
創(chuàng)建配置類,不需要?jiǎng)?chuàng)建
xml
配置文件
@Configuration
@ComponentScan(basePackages = {"aopanno"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {}
七、AOP 操作(AspectJ 配置文件)
1、創(chuàng)建兩個(gè)類,增強(qiáng)類和被增強(qiáng)類,創(chuàng)建方法
Book類
public class Book {public void buy() {System.out.println("buy.............");}
}
BookProxy類
public class BookProxy {public void before() {System.out.println("before.........");}
}
2、在 spring 配置文件中創(chuàng)建兩個(gè)類對(duì)象
<!--創(chuàng)建對(duì)象--><bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean><bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>
3、在 spring 配置文件中配置切入點(diǎn)
<!--配置aop增強(qiáng)--><aop:config><!--切入點(diǎn)--><aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopxml.Book.buy(..))"/><!--配置切面--><aop:aspect ref="bookProxy"><!--增強(qiáng)作用在具體的方法上--><aop:before method="before" pointcut-ref="p"/></aop:aspect></aop:config>