1,AOP簡介
1.1 什么是AOP?
- AOP(Aspect Oriented Programming)面向切面編程,一種編程范式,指導(dǎo)開發(fā)者如何組織程序結(jié)構(gòu)。
- OOP(Object Oriented Programming)面向?qū)ο缶幊?/li>
我們都知道OOP是一種編程思想,那么AOP也是一種編程思想,編程思想主要的內(nèi)容就是指導(dǎo)程序員該如何編寫程序,所以它們兩個是不同的編程范式
。
1.2 AOP作用
- 作用:在不驚動原始設(shè)計的基礎(chǔ)上為其進(jìn)行功能增強(qiáng),前面咱們有技術(shù)就可以實(shí)現(xiàn)這樣的功能即代理模式。
前面咱們有技術(shù)就可以實(shí)現(xiàn)這樣的功能即代理模式
。
1.3 AOP核心概念
為了能更好的理解AOP的相關(guān)概念,先準(zhǔn)備了一個環(huán)境,整個環(huán)境的內(nèi)容我們暫時可以不用關(guān)注,最主要的類為:BookDaoImpl
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
//記錄程序當(dāng)前執(zhí)行執(zhí)行(開始時間)
Long startTime = System.currentTimeMillis();
//業(yè)務(wù)執(zhí)行萬次
for (int i = 0;i<10000;i++) {
System.out.println("book dao save ...");
}
//記錄程序當(dāng)前執(zhí)行時間(結(jié)束時間)
Long endTime = System.currentTimeMillis();
//計算時間差
Long totalTime = endTime-startTime;
//輸出信息
System.out.println("執(zhí)行萬次消耗時間:" + totalTime + "ms");
}
public void update(){
System.out.println("book dao update ...");
}
public void delete(){
System.out.println("book dao delete ...");
}
public void select(){
System.out.println("book dao select ...");
}
}
代碼的內(nèi)容相信大家都能夠讀懂,對于save
方法中有計算萬次執(zhí)行消耗的時間。
當(dāng)在App類中從容器中獲取bookDao對象后,分別執(zhí)行其save
,delete
,update
和select
方法后會有如下的打印結(jié)果:
這個時候,我們就應(yīng)該有些疑問?
- 對于計算萬次執(zhí)行消耗的時間只有save方法有,為什么delete和update方法也會有呢?
- delete和update方法有,那什么select方法為什么又沒有呢?
這個案例中其實(shí)就使用了Spring的AOP,在不驚動(改動)原有設(shè)計(代碼)的前提下,想給誰添加功能就給誰添加。這個也就是Spring的理念:
- 無入侵式/無侵入式
說了這么多,Spring到底是如何實(shí)現(xiàn)的呢?
(1)Spring的AOP是對一個類的方法在不進(jìn)行任何修改的前提下實(shí)現(xiàn)增強(qiáng)。對于上面的案例中BookServiceImpl中有save
,update
,delete
和select
方法,這些方法我們給起了一個名字叫連接點(diǎn)。
(2)在BookServiceImpl的四個方法中,update
和delete
只有打印沒有計算萬次執(zhí)行消耗時間,但是在運(yùn)行的時候已經(jīng)有該功能,那也就是說update
和delete
方法都已經(jīng)被增強(qiáng),所以對于需要增強(qiáng)的方法我們給起了一個名字叫切入點(diǎn)。
(3)執(zhí)行BookServiceImpl的update和delete方法的時候都被添加了一個計算萬次執(zhí)行消耗時間的功能,將這個功能抽取到一個方法中,換句話說就是存放共性功能的方法,我們給起了個名字叫通知。
(4)通知是要增強(qiáng)的內(nèi)容,會有多個,切入點(diǎn)是需要被增強(qiáng)的方法,也會有多個,那哪個切入點(diǎn)需要添加哪個通知,就需要提前將它們之間的關(guān)系描述清楚,那么對于通知和切入點(diǎn)之間的關(guān)系描述,我們給起了個名字叫切面。
(5)通知是一個方法,方法不能獨(dú)立存在需要被寫在一個類中,這個類我們也給起了個名字叫通知類。
至此AOP中的核心概念就已經(jīng)介紹完了,總結(jié)下:
- 連接點(diǎn)(JoinPoint):程序執(zhí)行過程中的任意位置,粒度為執(zhí)行方法、拋出異常、設(shè)置變量等
- 在SpringAOP中,理解為方法的執(zhí)行
- 切入點(diǎn)(Pointcut):匹配連接點(diǎn)的式子
- 在SpringAOP中,一個切入點(diǎn)可以描述一個具體方法,也可也匹配多個方法
- 一個具體的方法:如com.itheima.dao包下的BookDao接口中的無形參無返回值的save方法
- 匹配多個方法:所有的save方法,所有的get開頭的方法,所有以Dao結(jié)尾的接口中的任意方法,所有帶有一個參數(shù)的方法
- 連接點(diǎn)范圍要比切入點(diǎn)范圍大,是切入點(diǎn)的方法也一定是連接點(diǎn),但是是連接點(diǎn)的方法就不一定要被增強(qiáng),所以可能不是切入點(diǎn)。
- 在SpringAOP中,一個切入點(diǎn)可以描述一個具體方法,也可也匹配多個方法
- 通知(Advice):在切入點(diǎn)處執(zhí)行的操作,也就是共性功能
- 在SpringAOP中,功能最終以方法的形式呈現(xiàn)
- 通知類:定義通知的類
- 切面(Aspect):描述通知與切入點(diǎn)的對應(yīng)關(guān)系。
2,AOP入門案例
2.1 需求分析
案例設(shè)定:測算接口執(zhí)行效率,但是這個案例稍微復(fù)雜了點(diǎn),我們對其進(jìn)行簡化。
簡化設(shè)定:在方法執(zhí)行前輸出當(dāng)前系統(tǒng)時間。
總結(jié)需求為:使用SpringAOP的注解方式完成在方法執(zhí)行的前打印出當(dāng)前系統(tǒng)時間。
2.2 思路分析
需求明確后,具體該如何實(shí)現(xiàn),都有哪些步驟,我們先來分析下:
1.導(dǎo)入坐標(biāo)(pom.xml)
2.制作連接點(diǎn)(原始操作,Dao接口與實(shí)現(xiàn)類)
3.制作共性功能(通知類與通知)
4.定義切入點(diǎn)
5.綁定切入點(diǎn)與通知關(guān)系(切面)
2.3 環(huán)境準(zhǔn)備
-
創(chuàng)建一個Maven項(xiàng)目
-
pom.xml添加Spring依賴
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>
-
添加BookDao和BookDaoImpl類
public interface BookDao { public void save(); public void update(); } @Repository public class BookDaoImpl implements BookDao { public void save() { System.out.println(System.currentTimeMillis()); System.out.println("book dao save ..."); } public void update(){ System.out.println("book dao update ..."); } }
-
創(chuàng)建Spring的配置類
@Configuration @ComponentScan("com.itheima") public class SpringConfig { }
-
編寫App運(yùn)行類
public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = ctx.getBean(BookDao.class); bookDao.save(); } }
最終創(chuàng)建好的項(xiàng)目結(jié)構(gòu)如下:
說明:
- 目前打印save方法的時候,因?yàn)榉椒ㄖ杏写蛴∠到y(tǒng)時間,所以運(yùn)行的時候是可以看到系統(tǒng)時間
- 對于update方法來說,就沒有該功能
- 我們要使用SpringAOP的方式在不改變update方法的前提下讓其具有打印系統(tǒng)時間的功能。
2.4 AOP實(shí)現(xià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思想的一個具體實(shí)現(xiàn),Spring有自己的AOP實(shí)現(xiàn),但是相比于AspectJ來說比較麻煩,所以我們直接采用Spring整合ApsectJ的方式進(jìn)行AOP開發(fā)。
步驟2:定義接口與實(shí)現(xiàn)類
環(huán)境準(zhǔn)備的時候,BookDaoImpl已經(jīng)準(zhǔn)備好,不需要做任何修改
步驟3:定義通知類和通知
通知就是將共性功能抽取出來后形成的方法,共性功能指的就是當(dāng)前系統(tǒng)時間的打印。
public class MyAdvice {
public void method(){
System.out.println(System.currentTimeMillis());
}
}
類名和方法名沒有要求,可以任意。
步驟4:定義切入點(diǎn)
BookDaoImpl中有兩個方法,分別是save和update,我們要增強(qiáng)的是update方法,該如何定義呢?
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
public void method(){
System.out.println(System.currentTimeMillis());
}
}
說明:
- 切入點(diǎn)定義依托一個不具有實(shí)際意義的方法進(jìn)行,即無參數(shù)、無返回值、方法體無實(shí)際邏輯。
步驟5:制作切面
切面是用來描述通知和切入點(diǎn)之間的關(guān)系,如何進(jìn)行關(guān)系的綁定?
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
綁定切入點(diǎn)與通知關(guān)系,并指定通知添加到原始連接點(diǎn)的具體執(zhí)行位置
說明:@Before翻譯過來是之前,也就是說通知會在切入點(diǎn)方法執(zhí)行之前執(zhí)行,除此之前還有其他四種類型,后面會講。
步驟6:將通知類配給容器并標(biāo)識其為切面類
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
步驟7:開啟注解格式AOP功能
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}
步驟8:運(yùn)行程序
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.update();
}
}
看到在執(zhí)行update方法之前打印了系統(tǒng)時間戳,說明對原始方法進(jìn)行了增強(qiáng),AOP編程成功。
知識點(diǎn)1:@EnableAspectJAutoProxy
名稱 | @EnableAspectJAutoProxy |
---|---|
類型 | 配置類注解 |
位置 | 配置類定義上方 |
作用 | 開啟注解格式AOP功能 |
知識點(diǎn)2:@Aspect
名稱 | @Aspect |
---|---|
類型 | 類注解 |
位置 | 切面類定義上方 |
作用 | 設(shè)置當(dāng)前類為AOP切面類 |
知識點(diǎn)3:@Pointcut
名稱 | @Pointcut |
---|---|
類型 | 方法注解 |
位置 | 切入點(diǎn)方法定義上方 |
作用 | 設(shè)置切入點(diǎn)方法 |
屬性 | value(默認(rèn)):切入點(diǎn)表達(dá)式 |
知識點(diǎn)4:@Before
名稱 | @Before |
---|---|
類型 | 方法注解 |
位置 | 通知方法定義上方 |
作用 | 設(shè)置當(dāng)前通知方法與切入點(diǎn)之間的綁定關(guān)系,當(dāng)前通知方法在原始切入點(diǎn)方法前運(yùn)行 |
3,AOP工作流程
3.1 AOP工作流程
由于AOP是基于Spring容器管理的bean做的增強(qiáng),所以整個工作過程需要從Spring加載bean說起:
流程1:Spring容器啟動
- 容器啟動就需要去加載bean,哪些類需要被加載呢?
- 需要被增強(qiáng)的類,如:BookServiceImpl
- 通知類,如:MyAdvice
- 注意此時bean對象還沒有創(chuàng)建成功
流程2:讀取所有切面配置中的切入點(diǎn)
- 上面這個例子中有兩個切入點(diǎn)的配置,但是第一個
ptx()
并沒有被使用,所以不會被讀取。
流程3:初始化bean,
判定bean對應(yīng)的類中的方法是否匹配到任意切入點(diǎn)
-
注意第1步在容器啟動的時候,bean對象還沒有被創(chuàng)建成功。
-
要被實(shí)例化bean對象的類中的方法和切入點(diǎn)進(jìn)行匹配
- 匹配失敗,創(chuàng)建原始對象,如
UserDao
- 匹配失敗說明不需要增強(qiáng),直接調(diào)用原始對象的方法即可。
- 匹配成功,創(chuàng)建原始對象(目標(biāo)對象)的代理對象,如:
BookDao
- 匹配成功說明需要對其進(jìn)行增強(qiáng)
- 對哪個類做增強(qiáng),這個類對應(yīng)的對象就叫做目標(biāo)對象
- 因?yàn)橐獙δ繕?biāo)對象進(jìn)行功能增強(qiáng),而采用的技術(shù)是動態(tài)代理,所以會為其創(chuàng)建一個代理對象
- 最終運(yùn)行的是代理對象的方法,在該方法中會對原始方法進(jìn)行功能增強(qiáng)
- 匹配失敗,創(chuàng)建原始對象,如
流程4:獲取bean執(zhí)行方法
- 獲取的bean是原始對象時,調(diào)用方法并執(zhí)行,完成操作
- 獲取的bean是代理對象時,根據(jù)代理對象的運(yùn)行模式運(yùn)行原始方法與增強(qiáng)的內(nèi)容,完成操作
驗(yàn)證容器中是否為代理對象
為了驗(yàn)證IOC容器中創(chuàng)建的對象和我們剛才所說的結(jié)論是否一致,首先先把結(jié)論理出來:
- 如果目標(biāo)對象中的方法會被增強(qiáng),那么容器中將存入的是目標(biāo)對象的代理對象
- 如果目標(biāo)對象中的方法不被增強(qiáng),那么容器中將存入的是目標(biāo)對象本身。
驗(yàn)證思路
1.要執(zhí)行的方法,不被定義的切入點(diǎn)包含,即不要增強(qiáng),打印當(dāng)前類的getClass()方法
2.要執(zhí)行的方法,被定義的切入點(diǎn)包含,即要增強(qiáng),打印出當(dāng)前類的getClass()方法
3.觀察兩次打印的結(jié)果
步驟1:修改App類,獲取類的類型
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
System.out.println(bookDao);
System.out.println(bookDao.getClass());
}
}
步驟2:修改MyAdvice類,不增強(qiáng)
因?yàn)槎x的切入點(diǎn)中,被修改成update1
,所以BookDao中的update方法在執(zhí)行的時候,就不會被增強(qiáng),
所以容器中的對象應(yīng)該是目標(biāo)對象本身。
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update1())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
步驟3:運(yùn)行程序
步驟4:修改MyAdvice類,增強(qiáng)
因?yàn)槎x的切入點(diǎn)中,被修改成update
,所以BookDao中的update方法在執(zhí)行的時候,就會被增強(qiáng),
所以容器中的對象應(yīng)該是目標(biāo)對象的代理對象
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
步驟5:運(yùn)行程序
至此對于剛才的結(jié)論,我們就得到了驗(yàn)證,這塊大家需要注意的是:
不能直接打印對象,從上面兩次結(jié)果中可以看出,直接打印對象走的是對象的toString方法,不管是不是代理對象打印的結(jié)果都是一樣的,原因是內(nèi)部對toString方法進(jìn)行了重寫。
3.2 AOP核心概念
在上面介紹AOP的工作流程中,我們提到了兩個核心概念,分別是:
- 目標(biāo)對象(Target):原始功能去掉共性功能對應(yīng)的類產(chǎn)生的對象,這種對象是無法直接完成最終工作的
- 代理(Proxy):目標(biāo)對象無法直接完成工作,需要對其進(jìn)行功能回填,通過原始對象的代理對象實(shí)現(xiàn)
上面這兩個概念比較抽象,簡單來說,
目標(biāo)對象就是要增強(qiáng)的類[如:BookServiceImpl類]對應(yīng)的對象,也叫原始對象,不能說它不能運(yùn)行,只能說它在運(yùn)行的過程中對于要增強(qiáng)的內(nèi)容是缺失的。
SpringAOP是在不改變原有設(shè)計(代碼)的前提下對其進(jìn)行增強(qiáng)的,它的底層采用的是代理模式實(shí)現(xiàn)的,所以要對原始對象進(jìn)行增強(qiáng),就需要對原始對象創(chuàng)建代理對象,在代理對象中的方法把通知[如:MyAdvice中的method方法]內(nèi)容加進(jìn)去,就實(shí)現(xiàn)了增強(qiáng),這就是我們所說的代理(Proxy)。文章來源:http://www.zghlxwxcb.cn/news/detail-772182.html
后記
????????美好的一天,到此結(jié)束,下次繼續(xù)努力!欲知后續(xù),請看下回分解,寫作不易,感謝大家的支持??! ??????文章來源地址http://www.zghlxwxcb.cn/news/detail-772182.html
到了這里,關(guān)于Spring AOP入門指南:輕松掌握面向切面編程的基礎(chǔ)知識的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!