一篇文章搞懂spring aop源碼
Aop是什么
與OOP對(duì)比,面向切面,傳統(tǒng)的OOP開(kāi)發(fā)中的代碼邏輯是自上而下的,而這些過(guò)程會(huì)產(chǎn)生一些橫切性問(wèn)題,這些橫切性的問(wèn)題和我們的主業(yè)務(wù)邏輯關(guān)系不大,這些橫切性問(wèn)題不會(huì)影響到主邏輯實(shí)現(xiàn)的,但是會(huì)散落到代碼的各個(gè)部分,難以維護(hù)。AOP是處理一些橫切性問(wèn)題,AOP的編程思想就是把這些問(wèn)題和主業(yè)務(wù)邏輯分開(kāi),達(dá)到與主業(yè)務(wù)邏輯解耦的目的。使代碼的重用性和開(kāi)發(fā)效率更高。
aop的應(yīng)用場(chǎng)景
日志記錄
權(quán)限驗(yàn)證
效率檢查
事務(wù)管理
exception
springAop的底層技術(shù)

springAop和AspectJ的關(guān)系
Aop是一種概念
springAop、AspectJ都是Aop的實(shí)現(xiàn),SpringAop有自己的語(yǔ)法,但是語(yǔ)法復(fù)雜,所以SpringAop借助了AspectJ的注解,但是底層實(shí)現(xiàn)還是自己的
spring?AOP提供兩種編程風(fēng)格
@AspectJ support ? ? ? ? ------------>利用aspectj的注解
Schema-based AOP support ----------->xml aop:config 命名空間
證明:spring,通過(guò)源 ?碼分析了,我們可以知道spring底層使用的是JDK或者CGLIB來(lái)完成的代理,并且在官網(wǎng)上spring給出了aspectj的文檔,和springAOP是不同的
spring Aop的概念
aspect:一定要給spring去管理 ?抽象 ?aspectj->類
pointcut:切點(diǎn)表示連接點(diǎn)的集合 ?-------------------> ? ? ? ? ? 表
(我的理解:PointCut是JoinPoint的謂語(yǔ),這是一個(gè)動(dòng)作,主要是告訴通知連接點(diǎn)在哪里,切點(diǎn)表達(dá)式?jīng)Q定?JoinPoint?的數(shù)量)
Joinpoint:連接點(diǎn) ? 目標(biāo)對(duì)象中的方法?----------------> ? ?記錄
(我的理解:JoinPoint是要關(guān)注和增強(qiáng)的方法,也就是我們要作用的點(diǎn))
Weaving?:把代理邏輯加入到目標(biāo)對(duì)象上的過(guò)程叫做織入
target?目標(biāo)對(duì)象 原始對(duì)象
aop?Proxy?代理對(duì)象 ?包含了原始對(duì)象的代碼和增加后的代碼的那個(gè)對(duì)象
advice:通知 ? ?(位置 +?logic)
advice通知類型:
Before?連接點(diǎn)執(zhí)行之前,但是無(wú)法阻止連接點(diǎn)的正常執(zhí)行,除非該段執(zhí)行拋出異常
After??連接點(diǎn)正常執(zhí)行之后,執(zhí)行過(guò)程中正常執(zhí)行返回退出,非異常退出
After?throwing??執(zhí)行拋出異常的時(shí)候
After?(finally) ?無(wú)論連接點(diǎn)是正常退出還是異常退出,都會(huì)執(zhí)行
Around?advice: 圍繞連接點(diǎn)執(zhí)行,例如方法調(diào)用。這是最有用的切面方式。around通知可以在方法調(diào)用之前和之后執(zhí)行自定義行為。它還負(fù)責(zé)選擇是繼續(xù)加入點(diǎn)還是通過(guò)返回自己的返回值或拋出異常來(lái)快速建議的方法執(zhí)行。
Proceedingjoinpoint?和JoinPoint的區(qū)別:
Proceedingjoinpoint?繼承了JoinPoint,proceed()這個(gè)是aop代理鏈執(zhí)行的方法。并擴(kuò)充實(shí)現(xiàn)了proceed()方法,用于繼續(xù)執(zhí)行連接點(diǎn)。JoinPoint僅能獲取相關(guān)參數(shù),無(wú)法執(zhí)行連接點(diǎn)。
JoinPoint的方法
1.java.lang.Object[]?getArgs():獲取連接點(diǎn)方法運(yùn)行時(shí)的入?yún)⒘斜恚?
2.Signature?getSignature() :獲取連接點(diǎn)的方法簽名對(duì)象;?
3.java.lang.Object?getTarget() :獲取連接點(diǎn)所在的目標(biāo)對(duì)象;?
4.java.lang.Object?getThis() :獲取代理對(duì)象本身;
proceed()有重載,有個(gè)帶參數(shù)的方法,可以修改目標(biāo)方法的的參數(shù)
Introductions
perthis
使用方式如下:
@Aspect("perthis(this(com.chenss.dao.IndexDaoImpl))")
要求:
1. AspectJ對(duì)象的注入類型為prototype
2. 目標(biāo)對(duì)象也必須是prototype的
原因?yàn)椋褐挥心繕?biāo)對(duì)象是原型模式的,每次getBean得到的對(duì)象才是不一樣的,由此針對(duì)每個(gè)對(duì)象就會(huì)產(chǎn)生新的切面對(duì)象,才能產(chǎn)生不同的切面結(jié)果。
springAop支持AspectJ
1、啟用@AspectJ支持
使用Java Configuration啟用@AspectJ支持
要使用Java @Configuration啟用@AspectJ支持,請(qǐng)?zhí)砑覢EnableAspectJAutoProxy注釋
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
使用XML配置啟用@AspectJ支持
要使用基于xml的配置啟用@AspectJ支持,可以使用aop:aspectj-autoproxy元素
<aop:aspectj-autoproxy/>
2、聲明一個(gè)Aspect
申明一個(gè)@Aspect注釋類,并且定義成一個(gè)bean交給Spring管理。
@Component
@Aspect
public class UserAspect {
}
3、申明一個(gè)pointCut
切入點(diǎn)表達(dá)式由@Pointcut注釋表示。切入點(diǎn)聲明由兩部分組成:一個(gè)簽名包含名稱和任何參數(shù),以及一個(gè)切入點(diǎn)表達(dá)式,該表達(dá)式確定我們對(duì)哪個(gè)方法執(zhí)行感興趣。
@Pointcut("execution(* transfer(..))")// 切入點(diǎn)表達(dá)式
private?void?anyOldTransfer() {}// 切入點(diǎn)簽名
切入點(diǎn)確定感興趣的?join points(連接點(diǎn)),從而使我們能夠控制何時(shí)執(zhí)行通知。Spring AOP只支持Spring bean的方法執(zhí)行?join points(連接點(diǎn)),所以您可以將切入點(diǎn)看作是匹配Spring bean上方法的執(zhí)行。
/**
* 申明Aspect,并且交給spring容器管理
*/
@Component
@Aspect
public class UserAspect {
/**
? ? * 申明切入點(diǎn),匹配UserDao所有方法調(diào)用
? ? * execution匹配方法執(zhí)行連接點(diǎn)
? ? * within:將匹配限制為特定類型中的連接點(diǎn)
? ? * args:參數(shù)
? ? * target:目標(biāo)對(duì)象
? ? * this:代理對(duì)象
? ? */
@Pointcut("execution(* com.yao.dao.UserDao.*(..))")
public void pintCut(){
System.out.println("point cut");
}
4、申明一個(gè)Advice通知
advice通知與pointcut切入點(diǎn)表達(dá)式相關(guān)聯(lián),并在切入點(diǎn)匹配的方法執(zhí)行@Before之前、@After之后或前后運(yùn)行。
/**
* 申明Aspect,并且交給spring容器管理
*/
@Component
@Aspect
public class UserAspect {
/**
? ? * 申明切入點(diǎn),匹配UserDao所有方法調(diào)用
? ? * execution匹配方法執(zhí)行連接點(diǎn)
? ? * within:將匹配限制為特定類型中的連接點(diǎn)
? ? * args:參數(shù)
? ? * target:目標(biāo)對(duì)象
? ? * this:代理對(duì)象
? ? */
@Pointcut("execution(* com.yao.dao.UserDao.*(..))")
public void pintCut(){
System.out.println("point cut");
}
/**
? ? * 申明before通知,在pintCut切入點(diǎn)前執(zhí)行
? ? * 通知與切入點(diǎn)表達(dá)式相關(guān)聯(lián),
? ? * 并在切入點(diǎn)匹配的方法執(zhí)行之前、之后或前后運(yùn)行。
? ? * 切入點(diǎn)表達(dá)式可以是對(duì)指定切入點(diǎn)的簡(jiǎn)單引用,也可以是在適當(dāng)位置聲明的切入點(diǎn)表達(dá)式。
? ? */
@Before("com.yao.aop.UserAspect.pintCut()")
public?void?beforeAdvice(){
System.out.println("before");
}
}
各種連接點(diǎn)joinPoint的意義:
1 .?execution
用于匹配方法執(zhí)行?join points連接點(diǎn),最小粒度方法,在aop中主要使用。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
這里問(wèn)號(hào)表示當(dāng)前項(xiàng)可以有也可以沒(méi)有,其中各項(xiàng)的語(yǔ)義如下
modifiers-pattern:方法的可見(jiàn)性,如public,protected;
ret-type-pattern:方法的返回值類型,如int,void等;
declaring-type-pattern:方法所在類的全路徑名,如com.spring.Aspect;
name-pattern:方法名類型,如buisinessService();
param-pattern:方法的參數(shù)類型,如java.lang.String;
throws-pattern:方法拋出的異常類型,如java.lang.Exception;
example:
@Pointcut("execution(* com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和類的任意方法
@Pointcut("execution(public * com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和類的public方法
@Pointcut("execution(public * com.chenss.dao.*.*())")//匹配com.chenss.dao包下的任意接口和類的public 無(wú)方法參數(shù)的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")//匹配com.chenss.dao包下的任意接口和類的第一個(gè)參數(shù)為String類型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和類的只有一個(gè)參數(shù),且參數(shù)為String類型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和類的只有一個(gè)參數(shù),且參數(shù)為String類型的方法
@Pointcut("execution(public * *(..))")//匹配任意的public方法
@Pointcut("execution(* te*(..))")//匹配任意的以te開(kāi)頭的方法
@Pointcut("execution(* com.chenss.dao.IndexDao.*(..))")//匹配com.chenss.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.chenss.dao..*.*(..))")//匹配com.chenss.dao包及其子包中任意的方法
關(guān)于這個(gè)表達(dá)式的詳細(xì)寫(xiě)法,可以腦補(bǔ)也可以參考官網(wǎng)很容易的,可以作為一個(gè)看spring官網(wǎng)文檔的入門(mén),打破你害怕看官方文檔的心理,其實(shí)你會(huì)發(fā)覺(jué)官方文檔也是很容易的
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples
由于Spring切面粒度最小是達(dá)到方法級(jí)別,而execution表達(dá)式可以用于明確指定方法返回類型,類名,方法名和參數(shù)名等與方法相關(guān)的信息,并且在Spring中,大部分需要使用AOP的業(yè)務(wù)場(chǎng)景也只需要達(dá)到方法級(jí)別即可,因而execution表達(dá)式的使用是最為廣泛的
2 .?within
?表達(dá)式的最小粒度為類
// ------------
// within與execution相比,粒度更大,僅能實(shí)現(xiàn)到包和接口、類級(jí)別。而execution可以精確到方法的返回值,參數(shù)個(gè)數(shù)、修飾符、參數(shù)類型等
@Pointcut("within(com.chenss.dao.*)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法
3?.?args
?args表達(dá)式的作用是匹配指定參數(shù)類型和指定參數(shù)數(shù)量的方法,與包名和類名無(wú)關(guān)
/**
* args同execution不同的地方在于:
* args匹配的是運(yùn)行時(shí)傳遞給方法的參數(shù)類型
* execution(* *(java.io.Serializable))匹配的是方法在聲明時(shí)指定的方法參數(shù)類型。
*/
@Pointcut("args(java.io.Serializable)")//匹配運(yùn)行時(shí)傳遞的參數(shù)類型為指定類型的、且參數(shù)個(gè)數(shù)和順序匹配
@Pointcut("@args(com.chenss.anno.Chenss)")//接受一個(gè)參數(shù),并且傳遞的參數(shù)的運(yùn)行時(shí)類型具有@Classified
4 . this JDK代理時(shí),指向接口和代理類proxy,cglib代理時(shí) 指向接口和子類(不使用proxy)
5?.?target ?指向接口和子類
/**
* 此處需要注意的是,如果配置設(shè)置proxyTargetClass=false,或默認(rèn)為false,則是用JDK代理,否則使用的是CGLIB代理
* JDK代理的實(shí)現(xiàn)方式是基于接口實(shí)現(xiàn),代理類繼承Proxy,實(shí)現(xiàn)接口。
* 而CGLIB繼承被代理的類來(lái)實(shí)現(xiàn)。
* 所以使用target會(huì)保證目標(biāo)不變,關(guān)聯(lián)對(duì)象不會(huì)受到這個(gè)設(shè)置的影響。
* 但是使用this對(duì)象時(shí),會(huì)根據(jù)該選項(xiàng)的設(shè)置,判斷是否能找到對(duì)象。
*/
@Pointcut("target(com.chenss.dao.IndexDaoImpl)")//目標(biāo)對(duì)象,也就是被代理的對(duì)象。限制目標(biāo)對(duì)象為com.chenss.dao.IndexDaoImpl類
@Pointcut("this(com.chenss.dao.IndexDaoImpl)")//當(dāng)前對(duì)象,也就是代理對(duì)象,代理對(duì)象時(shí)通過(guò)代理目標(biāo)對(duì)象的方式獲取新的對(duì)象,與原值并非一個(gè)
@Pointcut("@target(com.chenss.anno.Chenss)")//具有@Chenss的目標(biāo)對(duì)象中的任意方法
@Pointcut("@within(com.chenss.anno.Chenss)")//等同于@targ
這個(gè)比較難.......
proxy模式里面有兩個(gè)重要的術(shù)語(yǔ)
proxy?Class
target Class
CGLIB和JDK有區(qū)別 ? ?JDK是基于接口 ? cglib是基于繼承所有this可以在cglib作用
6 .?@annotation
這個(gè)很簡(jiǎn)單........
作用方法級(jí)別
上述所有表達(dá)式都有@ 比如@Target(里面是一個(gè)注解類xx,表示所有加了xx注解的類,和包名無(wú)關(guān))
注意:上述所有的表達(dá)式可以混合使用,|| && !
@Pointcut("@annotation(com.chenss.anno.Chenss)")//匹配帶有com.chenss.anno.Chenss注解的方法
7 .?bean
@Pointcut("bean(dao1)")//名稱為dao1的bean上的任意方法
@Pointcut("bean(dao*)")
Spring AOP XML實(shí)現(xiàn)方式的注意事項(xiàng):
在aop:config中定義切面邏輯,允許重復(fù)出現(xiàn),重復(fù)多次,以最后出現(xiàn)的邏輯為準(zhǔn),但是次數(shù)以出現(xiàn)的次數(shù)為準(zhǔn)
aop:aspect ID重復(fù)不影響正常運(yùn)行,依然能夠有正確結(jié)果
aop:pointcut ID重復(fù)會(huì)出現(xiàn)覆蓋,以最后出現(xiàn)的為準(zhǔn)。不同aop:aspect內(nèi)出現(xiàn)的pointcut配置,可以相互引用
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 定義開(kāi)始進(jìn)行注解掃描 -->
<context:component-scan base-package="com.chenss"></context:component-scan>
<!-- 定義AspectJ對(duì)象使用的邏輯類,類中提供切面之后執(zhí)行的邏輯方法 -->
<bean id="aspectAop" class="com.chenss.aspectj.Aspect"></bean>
<bean id="aspectAop2" class="com.chenss.aspectj.Aspect2"></bean>
<bean id="indexDao" class="com.chenss.entity.IndexDao"></bean>
<!--在Config中定義切面邏輯,允許重復(fù)出現(xiàn),重復(fù)多次,以最后出現(xiàn)的邏輯為準(zhǔn),但是次數(shù)以出現(xiàn)的次數(shù)為準(zhǔn)-->
<aop:config>
<!-- aop:aspect ID重復(fù)不影響正常運(yùn)行,依然能夠有正確結(jié)果 -->
<!-- aop:pointcut ID重復(fù)會(huì)出現(xiàn)覆蓋,以最后出現(xiàn)的為準(zhǔn)。不同aop:aspect內(nèi)出現(xiàn)的pointcut配置,可以相互引用 -->
<aop:aspect id="aspect" ref="aspectAop">
<aop:pointcut id="aspectCut" expression="execution(* com.chenss.entity.*.*())"/>
<aop:before method="before" pointcut-ref="aspectCut"></aop:before>
fffffff
<aop:pointcut id="aspectNameCut" expression="execution(* com.chenss.entity.*.*(java.lang.String, ..))"/>
<aop:before method="before2" pointcut-ref="aspectNameCut"></aop:before>
</aop:aspect>
</aop:config>
</beans>
spring AOP的源碼分析
cglib


cglib封裝了ASM這個(gè)開(kāi)源框架,對(duì)字節(jié)碼操作,完成對(duì)代理類的創(chuàng)建
主要通過(guò)集成目標(biāo)對(duì)象,然后完成重寫(xiě),再操作字節(jié)碼
具體看參考ASM的語(yǔ)法
JDK
在Proxy這個(gè)類當(dāng)中首先實(shí)例化一個(gè)對(duì)象ProxyClassFactory,然后在get方法中調(diào)用了apply方法,完成對(duì)代理類的創(chuàng)建


其中最重要的兩個(gè)方法
generateProxyClass通過(guò)反射收集字段和屬性然后生成字節(jié)
defineClass0 jvm內(nèi)部完成對(duì)上述字節(jié)的load

總結(jié):cglib是通過(guò)繼承來(lái)操作子類的字節(jié)碼生成代理類,JDK是通過(guò)接口,然后利用java反射完成對(duì)類的動(dòng)態(tài)創(chuàng)建,嚴(yán)格意義上來(lái)說(shuō)cglib的效率高于JDK的反射,但是這種效率取決于代碼功力,其實(shí)可以忽略不計(jì),畢竟JDK是JVM的親兒子........
spring5新特性
1 使用 lambda表達(dá)式定義bean
2 日志 spring4的日志是用jcl,原生的JCL,底層通過(guò)循環(huán)去加載具·體的日志實(shí)現(xiàn)技術(shù),所以有先后順序,spring5利用的是spring-jcl,其實(shí)就是spring自己改了JCL的代碼具體參考視頻當(dāng)中講的兩者的區(qū)別
新特性還有其他,但是這兩個(gè)比較重要,由于時(shí)間問(wèn)題,其他的特性可以去網(wǎng)上找到相應(yīng)資料,但是這兩個(gè)應(yīng)付面試絕對(duì)可以了,其他的特性噱頭居多,實(shí)用性可能不是很大。
如果想要深入的了解學(xué)習(xí)spring源碼,建議大家可以看這個(gè)視頻集
主要講解了spring ioc,spring aop,spring boot底層的源碼解析
