Spring超詳細(xì)講解面向?qū)ο蟮矫嫦蚯忻?/h1>
更新時(shí)間:2022年08月03日 15:49:57 作者:懶羊羊.java
面向?qū)ο缶幊淌且环N編程方式,此編程方式的落地需要使用“類”和 “對(duì)象”來(lái)實(shí)現(xiàn),所以,面向?qū)ο缶幊唐鋵?shí)就是對(duì) “類”和“對(duì)象” 的使用,面向切面編程,簡(jiǎn)單的說(shuō),就是動(dòng)態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程
前言
Object object = new Object();
世間萬(wàn)物的本質(zhì)都可看作類的對(duì)象,面向?qū)ο?OOP)的模式讓程序易維護(hù)、易復(fù)用、易擴(kuò)展,而面向切面(AOP)則是面向?qū)ο蟮难a(bǔ)充,讓對(duì)象的功能更加強(qiáng)大
對(duì)比前面的日志框架技術(shù)二者非常相似,他的特點(diǎn)就是在不影響業(yè)務(wù)的前提下將程序的運(yùn)行情況輸出到控制臺(tái),總體來(lái)看是起一個(gè)輔助的作用,所謂的AOP亦是如此——是在不改原有代碼的前提下對(duì)其進(jìn)行增強(qiáng)
一.OOP&AOP
OOP將組件視為對(duì)象,AOP將對(duì)象的切面視為“對(duì)象”
OOP&AOP讓程序通過(guò)極其簡(jiǎn)單的方式變得更加全面、強(qiáng)大

AOP(Aspect Oriented Programming)面向切面編程、OOP(Object Oriented Programming)面向?qū)ο缶幊?/p>
OOP是一種編程思想,AOP也是一種編程思想,編程思想主要的內(nèi)容就是指導(dǎo)程序員該如何編寫程序,兩者都是不同的編程范式各有特色
二.AOP核心
通過(guò)以下一個(gè)計(jì)算程序運(yùn)行時(shí)間的功能,引出AOP相關(guān)概念
@Repository
public class AImpl implements A {
public void save() {
//記錄程序當(dāng)前執(zhí)行執(zhí)行(開(kāi)始時(shí)間)
Long startTime = System.currentTimeMillis();
//業(yè)務(wù)執(zhí)行萬(wàn)次
for (int i = 0;i<10000;i++) {
System.out.println("START ...");
}
//記錄程序當(dāng)前執(zhí)行時(shí)間(結(jié)束時(shí)間)
Long endTime = System.currentTimeMillis();
//計(jì)算時(shí)間差
Long totalTime = endTime-startTime;
//輸出信息
System.out.println("執(zhí)行萬(wàn)次程序消耗時(shí)間:" + totalTime + "ms");
}
public void m1(){ System.out.println(" m1 ..."); }
public void m2(){ System.out.println(" m2 ..."); }
}
(1)save
,m1
和m2
方法,這些方法我們給起了一個(gè)名字叫連接點(diǎn)
(2)對(duì)于需要增強(qiáng)的方法我們給起了一個(gè)名字叫切入點(diǎn)
(3)將功能抽取到一個(gè)方法中,換句話說(shuō)就是存放共性功能的方法,我們給起了個(gè)名字叫通知
(4)通知是要增強(qiáng)的內(nèi)容,會(huì)有多個(gè),切入點(diǎn)是需要被增強(qiáng)的方法,也會(huì)有多個(gè),那哪個(gè)切入點(diǎn)需要添加哪個(gè)通知,就需要提前將它們之間的關(guān)系描述清楚,那么對(duì)于通知和切入點(diǎn)之間的關(guān)系描述,我們給起了個(gè)名字叫切面
(5)通知是一個(gè)方法,方法不能獨(dú)立存在需要被寫在一個(gè)類中,這個(gè)類我們也給起了個(gè)名字叫通知類

三.第一個(gè)AOP案例
1.環(huán)境準(zhǔn)備
- 創(chuàng)建一個(gè)Maven項(xiàng)目
- pom.xml添加Spring依賴
spring-context
- 添加A和AImpl類
public interface A {
public void save();
public void m1();
}
@Repository
public class AImpl implements A {
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save ...");
}
public void m1(){
System.out.println("book dao m1 ...");
}
}
創(chuàng)建Spring的配置類
@Configuration
@ComponentScan("yu7daily")
public class Config {
}
編寫Show運(yùn)行類
public class Show {
public static void main(String[] args) {
ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class);
A A = ctx.getBean(A.class);
A.save();
}
}
2.AOP實(shí)現(xiàn)步驟
1.@EnableAspectJAutoProxy 開(kāi)啟注解格式AOP功能
2.@Aspect設(shè)置當(dāng)前類為AOP切面類
3.@Pointcut 設(shè)置切入點(diǎn)方法
4.@Before設(shè)置當(dāng)前通知方法與切入點(diǎn)之間的綁定關(guān)系,當(dāng)前通知方法在原始切入點(diǎn)方法前運(yùn)行
**1.添加依賴
pom.xml
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
因?yàn)?code>spring-context中已經(jīng)導(dǎo)入了spring-aop
,所以不需要再單獨(dú)導(dǎo)入spring-aop
.
導(dǎo)入AspectJ的jar包,AspectJ是AOP思想的一個(gè)具體實(shí)現(xiàn),Spring有自己的AOP實(shí)現(xiàn),但是相比于AspectJ來(lái)說(shuō)比較麻煩,所以我們直接采用Spring整合ApsectJ的方式進(jìn)行AOP開(kāi)發(fā)。
2.定義接口與實(shí)現(xiàn)類:環(huán)境準(zhǔn)備的時(shí)候,AImpl已經(jīng)準(zhǔn)備好,不需要做任何修改
3.定義通知類和通知
通知就是將共性功能抽取出來(lái)后形成的方法,共性功能指的就是當(dāng)前系統(tǒng)時(shí)間的打印
public class Test {
public void method(){
System.out.println(System.currentTimeMillis());
}
}
類名和方法名沒(méi)有要求,可以任意。
4.定義切入點(diǎn)
AImpl中有兩個(gè)方法,分別是save和m1,我們要增強(qiáng)的是m1方法,該如何定義呢?
public class Test {
@Pointcut("execution(void yu7daily.dao.A.m1())")
private void po1(){}
public void method(){
System.out.println(System.currentTimeMillis());
}
}
說(shuō)明:
切入點(diǎn)定義依托一個(gè)不具有實(shí)際意義的方法進(jìn)行,即無(wú)參數(shù)、無(wú)返回值、方法體無(wú)實(shí)際邏輯。
execution及后面編寫的內(nèi)容
5.制作切面
切面是用來(lái)描述通知和切入點(diǎn)之間的關(guān)系,如何進(jìn)行關(guān)系的綁定?
public class Test {
@Pointcut("execution(void yu7daily.dao.A.m1())")
private void po1(){}
@Before("po1()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
綁定切入點(diǎn)與通知關(guān)系,并指定通知添加到原始連接點(diǎn)的具體執(zhí)行位置
說(shuō)明:@Before翻譯過(guò)來(lái)是之前,也就是說(shuō)通知會(huì)在切入點(diǎn)方法執(zhí)行之前執(zhí)行,除此之前還有其他四種類型
6.將通知類配給容器并標(biāo)識(shí)其為切面類
@Component
@Aspect
public class Test {
@Pointcut("execution(void yu7daily.dao.A.m1())")
private void po1(){}
@Before("po1()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
7.開(kāi)啟注解格式AOP功能
@Configuration
@ComponentScan("yu7daily")
@EnableAspectJAutoProxy
public class Config {
}
8.運(yùn)行程序
public class Show {
public static void main(String[] args) {
ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class);
A A = ctx.getBean(A.class);
A.m1();
}
}
看到在執(zhí)行m1方法之前打印了系統(tǒng)時(shí)間戳,說(shuō)明對(duì)原始方法進(jìn)行了增強(qiáng),AOP編程成功!!!
四.切入點(diǎn)表達(dá)式
前面的案例中,有涉及到如下內(nèi)容:
對(duì)于AOP中切入點(diǎn)表達(dá)式,我們總共會(huì)學(xué)習(xí)三個(gè)內(nèi)容,分別是語(yǔ)法格式、通配符和書寫技巧。
1.語(yǔ)法格式
首先我們先要明確兩個(gè)概念:
切入點(diǎn):要進(jìn)行增強(qiáng)的方法
切入點(diǎn)表達(dá)式:要進(jìn)行增強(qiáng)的方法的描述方式
描述方式一:執(zhí)行yu7daily.dao包下的A接口中的無(wú)參數(shù)m1方法
execution(void yu7daily.dao.A.m1())
描述方式二:執(zhí)行yu7daily.dao.impl包下的AImpl類中的無(wú)參數(shù)m1方法
execution(void yu7daily.dao.impl.AImpl.m1())
因?yàn)檎{(diào)用接口方法的時(shí)候最終運(yùn)行的還是其實(shí)現(xiàn)類的方法,所以上面兩種描述方式都是可以的。
對(duì)于切入點(diǎn)表達(dá)式的語(yǔ)法為:
切入點(diǎn)表達(dá)式標(biāo)準(zhǔn)格式:動(dòng)作關(guān)鍵字(訪問(wèn)修飾符 返回值 包名.類/接口名.方法名(參數(shù)) 異常名)
execution(public User yu7daily.service.UserService.findById(int))

切入點(diǎn)表達(dá)式就是要找到需要增強(qiáng)的方法,所以它就是對(duì)一個(gè)具體方法的描述,但是方法的定義會(huì)有很多,所以如果每一個(gè)方法對(duì)應(yīng)一個(gè)切入點(diǎn)表達(dá)式,極其復(fù)雜可以通過(guò)以下方式進(jìn)行簡(jiǎn)化
2.通配符
使用通配符描述切入點(diǎn),主要的目的就是簡(jiǎn)化之前的配置
*
:單個(gè)獨(dú)立的任意符號(hào),可以獨(dú)立出現(xiàn),也可以作為前綴或者后綴的匹配符出現(xiàn)
execution(public * yu7daily.*.UserService.find*(*))
匹配yu7daily包下的任意包中的UserService類或接口中所有find開(kāi)頭的帶有一個(gè)參數(shù)的方法
..
:多個(gè)連續(xù)的任意符號(hào),可以獨(dú)立出現(xiàn),常用于簡(jiǎn)化包名與參數(shù)的書寫
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService類或接口中所有名稱為findById的方法
+
:專用于匹配子類類型
execution(* *..*Service+.*(..))
使用切入點(diǎn)表達(dá)式來(lái)分析下:
execution(void yu7daily.dao.A.m1())
匹配接口,能匹配到
execution(void yu7daily.dao.impl.AImpl.m1())
匹配實(shí)現(xiàn)類,能匹配到
execution(* yu7daily.dao.impl.AImpl.m1())
返回值任意,能匹配到
execution(* yu7daily.dao.impl.AImpl.m1(*))
返回值任意,但是m1方法必須要有一個(gè)參數(shù),無(wú)法匹配,要想匹配需要在m1接口和實(shí)現(xiàn)類添加參數(shù)
execution(void com.*.*.*.*.m1())
返回值為void,com包下的任意包三層包下的任意類的m1方法,匹配到的是實(shí)現(xiàn)類,能匹配
execution(void com.*.*.*.m1())
返回值為void,com包下的任意兩層包下的任意類的m1方法,匹配到的是接口,能匹配
execution(void *..m1())
返回值為void,方法名是m1的任意包下的任意類,能匹配
execution(* *..*(..))
匹配項(xiàng)目中任意類的任意方法,能匹配,但是不建議使用這種方式,影響范圍廣
execution(* *..u*(..))
匹配項(xiàng)目中任意包任意類下只要以u(píng)開(kāi)頭的方法,m1方法能滿足,能匹配
execution(* *..*e(..))
匹配項(xiàng)目中任意包任意類下只要以e結(jié)尾的方法,m1和save方法能滿足,能匹配
execution(void com..*())
返回值為void,com包下的任意包任意類任意方法,能匹配,*代表的是方法
execution(* yu7daily.*.*Service.find*(..))
將項(xiàng)目中所有業(yè)務(wù)層方法的以find開(kāi)頭的方法匹配
execution(* yu7daily.*.*Service.save*(..))
將項(xiàng)目中所有業(yè)務(wù)層方法的以save開(kāi)頭的方法匹配
五.AOP通知類型
它所代表的含義是將通知添加到切入點(diǎn)方法執(zhí)行的前面。
除了這個(gè)注解外,還有沒(méi)有其他的注解,換個(gè)問(wèn)題就是除了可以在前面加,能不能在其他的地方加?
(1)前置通知,追加功能到方法執(zhí)行前,類似于在代碼1或者代碼2添加內(nèi)容
(2)后置通知,追加功能到方法執(zhí)行后,不管方法執(zhí)行的過(guò)程中有沒(méi)有拋出異常都會(huì)執(zhí)行,類似于在代碼5添加內(nèi)容
(3)返回后通知,追加功能到方法執(zhí)行后,只有方法正常執(zhí)行結(jié)束后才進(jìn)行,類似于在代碼3添加內(nèi)容,如果方法執(zhí)行拋出異常,返回后通知將不會(huì)被添加
(4)拋出異常后通知,追加功能到方法拋出異常后,只有方法執(zhí)行出異常才進(jìn)行,類似于在代碼4添加內(nèi)容,只有方法拋出異常后才會(huì)被添加
(5)環(huán)繞通知,環(huán)繞通知功能比較強(qiáng)大,它可以追加功能到方法執(zhí)行的前后,這也是比較常用的方式,它可以實(shí)現(xiàn)其他四種通知類型的功能
環(huán)境準(zhǔn)備
1.pom.xml添加Spring依賴spring-context、aspectjweaver
2.添加A和AImpl類
public interface A {
public void m1();
public int m2();
}
@Repository
public class AImpl implements A {
public void m1(){
System.out.println(" m1 ...");
}
public int m2() {
System.out.println(" m2 is running ...");
return 1;
}
}
創(chuàng)建Spring的配置類
@Configuration
@ComponentScan("yu7daily")
@EnableAspectJAutoProxy
public class Config {
}
創(chuàng)建通知類
@Component
@Aspect
public class Test {
@Pointcut("execution(void yu7daily.dao.A.m1())")
private void po1(){}
public void around(){
System.out.println("around before advice ...");
System.out.println("around after advice ...");
}
}
編寫Show運(yùn)行類
public class Show {
public static void main(String[] args) {
ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class);
A A = ctx.getBean(A.class);
A.m1();
}
}
環(huán)繞通知
(1)原始方法有返回值的處理
修改Test,對(duì)A中的m2方法添加環(huán)繞通知,
@Component
@Aspect
public class Test {
@Pointcut("execution(void yu7daily.dao.A.m1())")
private void po1(){}
@Pointcut("execution(int yu7daily.dao.A.m2())")
private void po2(){}
@Around("po2()")
public void aroundM2(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示對(duì)原始操作的調(diào)用
pjp.proceed();
System.out.println("around after advice ...");
}
}
修改Show類,調(diào)用m2方法
@Component
@Aspect
public class Test {
@Pointcut("execution(void yu7daily.dao.A.m1())")
private void po1(){}
@Pointcut("execution(int yu7daily.dao.A.m2())")
private void po2(){}
@Around("po2()")
public Object aroundM2(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示對(duì)原始操作的調(diào)用
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
}
說(shuō)明:
返回的是Object而不是int的主要原因是Object類型更通用隨時(shí)可以轉(zhuǎn)型
在環(huán)繞通知中是可以對(duì)原始方法返回值就行修改的
1.返回后通知
@Component
@Aspect
public class Test {
@Pointcut("execution(void yu7daily.dao.A.m1())")
private void po1(){}
@Pointcut("execution(int yu7daily.dao.A.m2())")
private void po2(){}
@AfterReturning("po2()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}
}
注意:返回后通知是需要在原始方法m2
正常執(zhí)行后才會(huì)被執(zhí)行,如果m2()
方法執(zhí)行的過(guò)程中出現(xiàn)了異常,那么返回后通知是不會(huì)被執(zhí)行。后置通知?jiǎng)t是不管原始方法有沒(méi)有拋出異常都會(huì)被執(zhí)行
2.異常后通知
@Component
@Aspect
public class Test {
@Pointcut("execution(void yu7daily.dao.A.m1())")
private void po1(){}
@Pointcut("execution(int yu7daily.dao.A.m2())")
private void po2(){}
@AfterReturning("po2()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
}
環(huán)繞通知注意事項(xiàng)
1. 環(huán)繞通知必須依賴形參ProceedingJoinPoint才能實(shí)現(xiàn)對(duì)原始方法的調(diào)用,進(jìn)而實(shí)現(xiàn)原始方法調(diào)用前后同時(shí)添加通知
2. 通知中如果未使用ProceedingJoinPoint對(duì)原始方法進(jìn)行調(diào)用將跳過(guò)原始方法的執(zhí)行
3. 對(duì)原始方法的調(diào)用可以不接收返回值,通知方法設(shè)置成void即可,如果接收返回值,最好設(shè)定為Object類型
4. 原始方法的返回值如果是void類型,通知方法的返回值類型可以設(shè)置成void,也可以設(shè)置成Object
5. 由于無(wú)法預(yù)知原始方法運(yùn)行后是否會(huì)拋出異常,因此環(huán)繞通知方法必須要處理Throwable異常
到此這篇關(guān)于Spring超詳細(xì)講解面向?qū)ο蟮矫嫦蚯忻娴奈恼戮徒榻B到這了,更多相關(guān)Spring面向?qū)ο髢?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
-
spring boot 加載web容器tomcat流程源碼分析
本文章主要描述spring boot加載web容器 tomcat的部分,為了避免文章知識(shí)點(diǎn)過(guò)于分散,其他相關(guān)的如bean的加載,tomcat內(nèi)部流程等不做深入討論,具體內(nèi)容詳情跟隨小編一起看看吧 2021-06-06
-
springboot2整合redis使用lettuce連接池的方法(解決lettuce連接池?zé)o效問(wèn)題)
這篇文章主要介紹了springboot2整合redis使用lettuce連接池(解決lettuce連接池?zé)o效問(wèn)題),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下 2020-12-12
-
Java設(shè)計(jì)模式七大原則之里氏替換原則詳解
在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,里氏替換原則(Liskov Substitution principle)是對(duì)子類型的特別定義。本文將為大家詳細(xì)介紹Java設(shè)計(jì)模式七大原則之一的里氏替換原則,需要的可以參考一下 2022-02-02
最新評(píng)論
前言
Object object = new Object();
世間萬(wàn)物的本質(zhì)都可看作類的對(duì)象,面向?qū)ο?OOP)的模式讓程序易維護(hù)、易復(fù)用、易擴(kuò)展,而面向切面(AOP)則是面向?qū)ο蟮难a(bǔ)充,讓對(duì)象的功能更加強(qiáng)大
對(duì)比前面的日志框架技術(shù)二者非常相似,他的特點(diǎn)就是在不影響業(yè)務(wù)的前提下將程序的運(yùn)行情況輸出到控制臺(tái),總體來(lái)看是起一個(gè)輔助的作用,所謂的AOP亦是如此——是在不改原有代碼的前提下對(duì)其進(jìn)行增強(qiáng)
一.OOP&AOP
OOP將組件視為對(duì)象,AOP將對(duì)象的切面視為“對(duì)象”
OOP&AOP讓程序通過(guò)極其簡(jiǎn)單的方式變得更加全面、強(qiáng)大
AOP(Aspect Oriented Programming)面向切面編程、OOP(Object Oriented Programming)面向?qū)ο缶幊?/p>
OOP是一種編程思想,AOP也是一種編程思想,編程思想主要的內(nèi)容就是指導(dǎo)程序員該如何編寫程序,兩者都是不同的編程范式各有特色
二.AOP核心
通過(guò)以下一個(gè)計(jì)算程序運(yùn)行時(shí)間的功能,引出AOP相關(guān)概念
@Repository public class AImpl implements A { public void save() { //記錄程序當(dāng)前執(zhí)行執(zhí)行(開(kāi)始時(shí)間) Long startTime = System.currentTimeMillis(); //業(yè)務(wù)執(zhí)行萬(wàn)次 for (int i = 0;i<10000;i++) { System.out.println("START ..."); } //記錄程序當(dāng)前執(zhí)行時(shí)間(結(jié)束時(shí)間) Long endTime = System.currentTimeMillis(); //計(jì)算時(shí)間差 Long totalTime = endTime-startTime; //輸出信息 System.out.println("執(zhí)行萬(wàn)次程序消耗時(shí)間:" + totalTime + "ms"); } public void m1(){ System.out.println(" m1 ..."); } public void m2(){ System.out.println(" m2 ..."); } }
(1)save
,m1
和m2
方法,這些方法我們給起了一個(gè)名字叫連接點(diǎn)
(2)對(duì)于需要增強(qiáng)的方法我們給起了一個(gè)名字叫切入點(diǎn)
(3)將功能抽取到一個(gè)方法中,換句話說(shuō)就是存放共性功能的方法,我們給起了個(gè)名字叫通知
(4)通知是要增強(qiáng)的內(nèi)容,會(huì)有多個(gè),切入點(diǎn)是需要被增強(qiáng)的方法,也會(huì)有多個(gè),那哪個(gè)切入點(diǎn)需要添加哪個(gè)通知,就需要提前將它們之間的關(guān)系描述清楚,那么對(duì)于通知和切入點(diǎn)之間的關(guān)系描述,我們給起了個(gè)名字叫切面
(5)通知是一個(gè)方法,方法不能獨(dú)立存在需要被寫在一個(gè)類中,這個(gè)類我們也給起了個(gè)名字叫通知類
三.第一個(gè)AOP案例
1.環(huán)境準(zhǔn)備
- 創(chuàng)建一個(gè)Maven項(xiàng)目
- pom.xml添加Spring依賴
spring-context
- 添加A和AImpl類
public interface A { public void save(); public void m1(); } @Repository public class AImpl implements A { public void save() { System.out.println(System.currentTimeMillis()); System.out.println("book dao save ..."); } public void m1(){ System.out.println("book dao m1 ..."); } }
創(chuàng)建Spring的配置類
@Configuration @ComponentScan("yu7daily") public class Config { }
編寫Show運(yùn)行類
public class Show { public static void main(String[] args) { ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class); A A = ctx.getBean(A.class); A.save(); } }
2.AOP實(shí)現(xiàn)步驟
1.@EnableAspectJAutoProxy 開(kāi)啟注解格式AOP功能
2.@Aspect設(shè)置當(dāng)前類為AOP切面類
3.@Pointcut 設(shè)置切入點(diǎn)方法
4.@Before設(shè)置當(dāng)前通知方法與切入點(diǎn)之間的綁定關(guān)系,當(dāng)前通知方法在原始切入點(diǎn)方法前運(yùn)行
**1.添加依賴
pom.xml
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
因?yàn)?code>spring-context中已經(jīng)導(dǎo)入了spring-aop
,所以不需要再單獨(dú)導(dǎo)入spring-aop
.
導(dǎo)入AspectJ的jar包,AspectJ是AOP思想的一個(gè)具體實(shí)現(xiàn),Spring有自己的AOP實(shí)現(xiàn),但是相比于AspectJ來(lái)說(shuō)比較麻煩,所以我們直接采用Spring整合ApsectJ的方式進(jìn)行AOP開(kāi)發(fā)。
2.定義接口與實(shí)現(xiàn)類:環(huán)境準(zhǔn)備的時(shí)候,AImpl已經(jīng)準(zhǔn)備好,不需要做任何修改
3.定義通知類和通知
通知就是將共性功能抽取出來(lái)后形成的方法,共性功能指的就是當(dāng)前系統(tǒng)時(shí)間的打印
public class Test { public void method(){ System.out.println(System.currentTimeMillis()); } }
類名和方法名沒(méi)有要求,可以任意。
4.定義切入點(diǎn)
AImpl中有兩個(gè)方法,分別是save和m1,我們要增強(qiáng)的是m1方法,該如何定義呢?
public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} public void method(){ System.out.println(System.currentTimeMillis()); } }
說(shuō)明:
切入點(diǎn)定義依托一個(gè)不具有實(shí)際意義的方法進(jìn)行,即無(wú)參數(shù)、無(wú)返回值、方法體無(wú)實(shí)際邏輯。
execution及后面編寫的內(nèi)容
5.制作切面
切面是用來(lái)描述通知和切入點(diǎn)之間的關(guān)系,如何進(jìn)行關(guān)系的綁定?
public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Before("po1()") public void method(){ System.out.println(System.currentTimeMillis()); } }
綁定切入點(diǎn)與通知關(guān)系,并指定通知添加到原始連接點(diǎn)的具體執(zhí)行位置
說(shuō)明:@Before翻譯過(guò)來(lái)是之前,也就是說(shuō)通知會(huì)在切入點(diǎn)方法執(zhí)行之前執(zhí)行,除此之前還有其他四種類型
6.將通知類配給容器并標(biāo)識(shí)其為切面類
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Before("po1()") public void method(){ System.out.println(System.currentTimeMillis()); } }
7.開(kāi)啟注解格式AOP功能
@Configuration @ComponentScan("yu7daily") @EnableAspectJAutoProxy public class Config { }
8.運(yùn)行程序
public class Show { public static void main(String[] args) { ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class); A A = ctx.getBean(A.class); A.m1(); } }
看到在執(zhí)行m1方法之前打印了系統(tǒng)時(shí)間戳,說(shuō)明對(duì)原始方法進(jìn)行了增強(qiáng),AOP編程成功!!!
四.切入點(diǎn)表達(dá)式
前面的案例中,有涉及到如下內(nèi)容:
對(duì)于AOP中切入點(diǎn)表達(dá)式,我們總共會(huì)學(xué)習(xí)三個(gè)內(nèi)容,分別是語(yǔ)法格式、通配符和書寫技巧。
1.語(yǔ)法格式
首先我們先要明確兩個(gè)概念:
切入點(diǎn):要進(jìn)行增強(qiáng)的方法
切入點(diǎn)表達(dá)式:要進(jìn)行增強(qiáng)的方法的描述方式
描述方式一:執(zhí)行yu7daily.dao包下的A接口中的無(wú)參數(shù)m1方法
execution(void yu7daily.dao.A.m1())
描述方式二:執(zhí)行yu7daily.dao.impl包下的AImpl類中的無(wú)參數(shù)m1方法
execution(void yu7daily.dao.impl.AImpl.m1())
因?yàn)檎{(diào)用接口方法的時(shí)候最終運(yùn)行的還是其實(shí)現(xiàn)類的方法,所以上面兩種描述方式都是可以的。
對(duì)于切入點(diǎn)表達(dá)式的語(yǔ)法為:
切入點(diǎn)表達(dá)式標(biāo)準(zhǔn)格式:動(dòng)作關(guān)鍵字(訪問(wèn)修飾符 返回值 包名.類/接口名.方法名(參數(shù)) 異常名)
execution(public User yu7daily.service.UserService.findById(int))
切入點(diǎn)表達(dá)式就是要找到需要增強(qiáng)的方法,所以它就是對(duì)一個(gè)具體方法的描述,但是方法的定義會(huì)有很多,所以如果每一個(gè)方法對(duì)應(yīng)一個(gè)切入點(diǎn)表達(dá)式,極其復(fù)雜可以通過(guò)以下方式進(jìn)行簡(jiǎn)化
2.通配符
使用通配符描述切入點(diǎn),主要的目的就是簡(jiǎn)化之前的配置
*
:單個(gè)獨(dú)立的任意符號(hào),可以獨(dú)立出現(xiàn),也可以作為前綴或者后綴的匹配符出現(xiàn)
execution(public * yu7daily.*.UserService.find*(*))
匹配yu7daily包下的任意包中的UserService類或接口中所有find開(kāi)頭的帶有一個(gè)參數(shù)的方法
..
:多個(gè)連續(xù)的任意符號(hào),可以獨(dú)立出現(xiàn),常用于簡(jiǎn)化包名與參數(shù)的書寫
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService類或接口中所有名稱為findById的方法
+
:專用于匹配子類類型
execution(* *..*Service+.*(..))
使用切入點(diǎn)表達(dá)式來(lái)分析下:
execution(void yu7daily.dao.A.m1())
匹配接口,能匹配到
execution(void yu7daily.dao.impl.AImpl.m1())
匹配實(shí)現(xiàn)類,能匹配到
execution(* yu7daily.dao.impl.AImpl.m1())
返回值任意,能匹配到
execution(* yu7daily.dao.impl.AImpl.m1(*))
返回值任意,但是m1方法必須要有一個(gè)參數(shù),無(wú)法匹配,要想匹配需要在m1接口和實(shí)現(xiàn)類添加參數(shù)
execution(void com.*.*.*.*.m1())
返回值為void,com包下的任意包三層包下的任意類的m1方法,匹配到的是實(shí)現(xiàn)類,能匹配
execution(void com.*.*.*.m1())
返回值為void,com包下的任意兩層包下的任意類的m1方法,匹配到的是接口,能匹配
execution(void *..m1())
返回值為void,方法名是m1的任意包下的任意類,能匹配
execution(* *..*(..))
匹配項(xiàng)目中任意類的任意方法,能匹配,但是不建議使用這種方式,影響范圍廣
execution(* *..u*(..))
匹配項(xiàng)目中任意包任意類下只要以u(píng)開(kāi)頭的方法,m1方法能滿足,能匹配
execution(* *..*e(..))
匹配項(xiàng)目中任意包任意類下只要以e結(jié)尾的方法,m1和save方法能滿足,能匹配
execution(void com..*())
返回值為void,com包下的任意包任意類任意方法,能匹配,*代表的是方法
execution(* yu7daily.*.*Service.find*(..))
將項(xiàng)目中所有業(yè)務(wù)層方法的以find開(kāi)頭的方法匹配
execution(* yu7daily.*.*Service.save*(..))
將項(xiàng)目中所有業(yè)務(wù)層方法的以save開(kāi)頭的方法匹配
五.AOP通知類型
它所代表的含義是將通知添加到切入點(diǎn)方法執(zhí)行的前面。
除了這個(gè)注解外,還有沒(méi)有其他的注解,換個(gè)問(wèn)題就是除了可以在前面加,能不能在其他的地方加?
(1)前置通知,追加功能到方法執(zhí)行前,類似于在代碼1或者代碼2添加內(nèi)容
(2)后置通知,追加功能到方法執(zhí)行后,不管方法執(zhí)行的過(guò)程中有沒(méi)有拋出異常都會(huì)執(zhí)行,類似于在代碼5添加內(nèi)容
(3)返回后通知,追加功能到方法執(zhí)行后,只有方法正常執(zhí)行結(jié)束后才進(jìn)行,類似于在代碼3添加內(nèi)容,如果方法執(zhí)行拋出異常,返回后通知將不會(huì)被添加
(4)拋出異常后通知,追加功能到方法拋出異常后,只有方法執(zhí)行出異常才進(jìn)行,類似于在代碼4添加內(nèi)容,只有方法拋出異常后才會(huì)被添加
(5)環(huán)繞通知,環(huán)繞通知功能比較強(qiáng)大,它可以追加功能到方法執(zhí)行的前后,這也是比較常用的方式,它可以實(shí)現(xiàn)其他四種通知類型的功能
環(huán)境準(zhǔn)備
1.pom.xml添加Spring依賴spring-context、aspectjweaver
2.添加A和AImpl類
public interface A { public void m1(); public int m2(); } @Repository public class AImpl implements A { public void m1(){ System.out.println(" m1 ..."); } public int m2() { System.out.println(" m2 is running ..."); return 1; } }
創(chuàng)建Spring的配置類
@Configuration @ComponentScan("yu7daily") @EnableAspectJAutoProxy public class Config { }
創(chuàng)建通知類
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} public void around(){ System.out.println("around before advice ..."); System.out.println("around after advice ..."); } }
編寫Show運(yùn)行類
public class Show { public static void main(String[] args) { ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class); A A = ctx.getBean(A.class); A.m1(); } }
環(huán)繞通知
(1)原始方法有返回值的處理
修改Test,對(duì)A中的m2方法添加環(huán)繞通知,
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Pointcut("execution(int yu7daily.dao.A.m2())") private void po2(){} @Around("po2()") public void aroundM2(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before advice ..."); //表示對(duì)原始操作的調(diào)用 pjp.proceed(); System.out.println("around after advice ..."); } }
修改Show類,調(diào)用m2方法
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Pointcut("execution(int yu7daily.dao.A.m2())") private void po2(){} @Around("po2()") public Object aroundM2(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before advice ..."); //表示對(duì)原始操作的調(diào)用 Object ret = pjp.proceed(); System.out.println("around after advice ..."); return ret; } }
說(shuō)明:
返回的是Object而不是int的主要原因是Object類型更通用隨時(shí)可以轉(zhuǎn)型
在環(huán)繞通知中是可以對(duì)原始方法返回值就行修改的
1.返回后通知
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Pointcut("execution(int yu7daily.dao.A.m2())") private void po2(){} @AfterReturning("po2()") public void afterReturning() { System.out.println("afterReturning advice ..."); } }
注意:返回后通知是需要在原始方法m2
正常執(zhí)行后才會(huì)被執(zhí)行,如果m2()
方法執(zhí)行的過(guò)程中出現(xiàn)了異常,那么返回后通知是不會(huì)被執(zhí)行。后置通知?jiǎng)t是不管原始方法有沒(méi)有拋出異常都會(huì)被執(zhí)行
2.異常后通知
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Pointcut("execution(int yu7daily.dao.A.m2())") private void po2(){} @AfterReturning("po2()") public void afterThrowing() { System.out.println("afterThrowing advice ..."); } }
環(huán)繞通知注意事項(xiàng)
1. 環(huán)繞通知必須依賴形參ProceedingJoinPoint才能實(shí)現(xiàn)對(duì)原始方法的調(diào)用,進(jìn)而實(shí)現(xiàn)原始方法調(diào)用前后同時(shí)添加通知
2. 通知中如果未使用ProceedingJoinPoint對(duì)原始方法進(jìn)行調(diào)用將跳過(guò)原始方法的執(zhí)行
3. 對(duì)原始方法的調(diào)用可以不接收返回值,通知方法設(shè)置成void即可,如果接收返回值,最好設(shè)定為Object類型
4. 原始方法的返回值如果是void類型,通知方法的返回值類型可以設(shè)置成void,也可以設(shè)置成Object
5. 由于無(wú)法預(yù)知原始方法運(yùn)行后是否會(huì)拋出異常,因此環(huán)繞通知方法必須要處理Throwable異常
到此這篇關(guān)于Spring超詳細(xì)講解面向?qū)ο蟮矫嫦蚯忻娴奈恼戮徒榻B到這了,更多相關(guān)Spring面向?qū)ο髢?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring boot 加載web容器tomcat流程源碼分析
本文章主要描述spring boot加載web容器 tomcat的部分,為了避免文章知識(shí)點(diǎn)過(guò)于分散,其他相關(guān)的如bean的加載,tomcat內(nèi)部流程等不做深入討論,具體內(nèi)容詳情跟隨小編一起看看吧2021-06-06springboot2整合redis使用lettuce連接池的方法(解決lettuce連接池?zé)o效問(wèn)題)
這篇文章主要介紹了springboot2整合redis使用lettuce連接池(解決lettuce連接池?zé)o效問(wèn)題),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12Java設(shè)計(jì)模式七大原則之里氏替換原則詳解
在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,里氏替換原則(Liskov Substitution principle)是對(duì)子類型的特別定義。本文將為大家詳細(xì)介紹Java設(shè)計(jì)模式七大原則之一的里氏替換原則,需要的可以參考一下2022-02-02