前面一篇文章分析了Spring AOP的原理,下面這篇文章對Spring AOP做一個簡單的介紹和總結。
一、Spring AOP的常見面試題目
1.AOP是什麼
與OOP對比,面向切面,傳統的OOP開發中的代碼邏輯是自上而下的,而這些過程會產生一些橫切性問題,這些橫切性的問題和我們的主業務邏輯關係不大,這些橫切性問題不會影響到主邏輯實現的,但是會散落到代碼的各個部分,難以維護。AOP是處理一些橫切性問題,AOP的編程思想就是把這些問題和主業務邏輯分開,達到與主業務邏輯解耦的目的。使代碼的重用性和開發效率更高。
2. AOP的應用場景
- 日誌記錄
- 權限驗證
- 效率檢查
- 事務管理
- exception
3.springAop和AspectJ的關係
springAop和AspectJ的關係
Aop是一種概念
springAop、AspectJ都是Aop的實現,SpringAop有自己的語法,但是語法複雜,所以SpringAop藉助了AspectJ的註解,但是底層實現還是自己的。
spring AOP提供兩種編程風格:
1.@AspectJ support ------------>利用aspectj的註解
2.Schema-based AOP support ----------->xml aop:config 命名空間
證明:spring,通過源 碼分析了,我們可以知道spring底層使用的是JDK或者CGLIB來完成的代理,並且在官網上spring給出了aspectj的文檔,和springAOP是不同的
二、Spring Aop的概念
aspect:一定要給spring去管理 抽象 aspectj->類
pointcut:切點表示連接點的集合 -------------------> 表
(我的理解:PointCut是JoinPoint的謂語,這是一個動作,主要是告訴通知連接點在哪裏,切點表達式決定 JoinPoint 的數量)
Joinpoint:連接點 目標對象中的方法 ----------------> 記錄
(我的理解:JoinPoint是要關注和增強的方法,也就是我們要作用的點)
Weaving :把代理邏輯加入到目標對象上的過程叫做織入
target:目標對象 原始對象
aop Proxy 代理對象 包含了原始對象的代碼和增加後的代碼的那個對象
advice:通知 (位置 + logic)
advice通知類型:
Before 連接點執行之前,但是無法阻止連接點的正常執行,除非該段執行拋出異常
After 連接點正常執行之後,執行過程中正常執行返回退出,非異常退出
After throwing 執行拋出異常的時候
After (finally) 無論連接點是正常退出還是異常退出,都會執行
Around advice: 圍繞連接點執行,例如方法調用。這是最有用的切面方式。around通知可以在方法調用之前和之後執行自定義行爲。它還負責選擇是繼續加入點還是通過返回自己的返回值或拋出異常來快速建議的方法執行。
Proceedingjoinpoint 和JoinPoint的區別:
Proceedingjoinpoint 繼承了JoinPoint,proceed()這個是aop代理鏈執行的方法。並擴充實現了proceed()方法,用於繼續執行連接點。JoinPoint僅能獲取相關參數,無法執行連接點。
JoinPoint的方法
1.java.lang.Object[] getArgs():獲取連接點方法運行時的入參列表;
2.Signature getSignature() :獲取連接點的方法簽名對象;
3.java.lang.Object getTarget() :獲取連接點所在的目標對象;
4.java.lang.Object getThis() :獲取代理對象本身;
proceed()有重載,有個帶參數的方法,可以修改目標方法的的參數
Introductions
perthis
使用方式如下:
@Aspect("perthis(this(com.chenss.dao.IndexDaoImpl))")
要求:
1. AspectJ對象的注入類型爲prototype
2. 目標對象也必須是prototype的
原因爲:只有目標對象是原型模式的,每次getBean得到的對象纔是不一樣的,由此針對每個對象就會產生新的切面對象,才能產生不同的切面結果。
三、SpringAop支持AspectJ
1、啓用@AspectJ支持
使用Java Configuration啓用@AspectJ支持
要使用Java @Configuration啓用@AspectJ支持,請添加@EnableAspectJAutoProxy註解。
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
使用XML配置啓用@AspectJ支持。
要使用基於xml的配置啓用@AspectJ支持,可以使用aop:aspectj-autoproxy元素。
<aop:aspectj-autoproxy/>
2、聲明一個Aspect
申明一個@Aspect註釋類,並且定義成一個bean交給Spring管理。
@Component
@Aspect
public class UserAspect {
}
3、申明一個pointCut
切入點表達式由@Pointcut註釋表示。切入點聲明由兩部分組成:一個簽名包含名稱和任何參數,以及一個切入點表達式,該表達式確定我們對哪個方法執行感興趣。
@Pointcut("execution(* transfer(..))")// 切入點表達式
private void anyOldTransfer() {}// 切入點簽名
切入點確定感興趣的 join points(連接點),從而使我們能夠控制何時執行通知。Spring AOP只支持Spring bean的方法執行 join points(連接點),所以您可以將切入點看作是匹配Spring bean上方法的執行。
/**
* 申明Aspect,並且交給spring容器管理
*/
@Component
@Aspect
public class UserAspect {
/**
* 申明切入點,匹配UserDao所有方法調用
* execution匹配方法執行連接點
* within:將匹配限制爲特定類型中的連接點
* args:參數
* target:目標對象
* this:代理對象
*/
@Pointcut("execution(* com.yao.dao.UserDao.*(..))")
public void pintCut(){
System.out.println("point cut");
}
4、申明一個Advice通知
advice通知與pointcut切入點表達式相關聯,並在切入點匹配的方法執行@Before之前、@After之後或前後運行。
/**
* 申明Aspect,並且交給spring容器管理
*/
@Component
@Aspect
public class UserAspect {
/**
* 申明切入點,匹配UserDao所有方法調用
* execution匹配方法執行連接點
* within:將匹配限制爲特定類型中的連接點
* args:參數
* target:目標對象
* this:代理對象
*/
@Pointcut("execution(* com.yao.dao.UserDao.*(..))")
public void pintCut(){
System.out.println("point cut");
}
/**
* 申明before通知,在pintCut切入點前執行
* 通知與切入點表達式相關聯,
* 並在切入點匹配的方法執行之前、之後或前後運行。
* 切入點表達式可以是對指定切入點的簡單引用,也可以是在適當位置聲明的切入點表達式。
*/
@Before("com.yao.aop.UserAspect.pintCut()")
public void beforeAdvice(){
System.out.println("before");
}
}
三.各種連接點joinPoint的意義、
1.execution
用於匹配方法執行 join points連接點,最小粒度方法,在aop中主要使用。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
這裏問號表示當前項可以有也可以沒有,其中各項的語義如下
modifiers-pattern:方法的可見性,如public,protected;
ret-type-pattern:方法的返回值類型,如int,void等;
declaring-type-pattern:方法所在類的全路徑名,如com.spring.Aspect;
name-pattern:方法名類型,如buisinessService();
param-pattern:方法的參數類型,如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 無方法參數的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")//匹配com.chenss.dao包下的任意接口和類的第一個參數爲String類型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和類的只有一個參數,且參數爲String類型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和類的只有一個參數,且參數爲String類型的方法
@Pointcut("execution(public * *(..))")//匹配任意的public方法
@Pointcut("execution(* te*(..))")//匹配任意的以te開頭的方法
@Pointcut("execution(* com.chenss.dao.IndexDao.*(..))")//匹配com.chenss.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.chenss.dao..*.*(..))")//匹配com.chenss.dao包及其子包中任意的方法
關於這個表達式的詳細寫法,可以腦補也可以參考官網很容易的,可以作爲一個看spring官網文檔的入門,打破你害怕看官方文檔的心理,其實你會發覺官方文檔也是很容易的。
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples
由於Spring切面粒度最小是達到方法級別,而execution表達式可以用於明確指定方法返回類型,類名,方法名和參數名等與方法相關的信息,並且在Spring中,大部分需要使用AOP的業務場景也只需要達到方法級別即可,因而execution表達式的使用是最爲廣泛的。
2. within
™表達式的最小粒度爲類
// ------------
// within與execution相比,粒度更大,僅能實現到包和接口、類級別。而execution可以精確到方法的返回值,參數個數、修飾符、參數類型等
@Pointcut("within(com.chenss.dao.*)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法
3.args
args表達式的作用是匹配指定參數類型和指定參數數量的方法,與包名和類名無關
/**
* args同execution不同的地方在於:
* args匹配的是運行時傳遞給方法的參數類型
* execution(* *(java.io.Serializable))匹配的是方法在聲明時指定的方法參數類型。
*/
@Pointcut("args(java.io.Serializable)")//匹配運行時傳遞的參數類型爲指定類型的、且參數個數和順序匹配
@Pointcut("@args(com.chenss.anno.Chenss)")//接受一個參數,並且傳遞的參數的運行時類型具有@Classified
4.this JDK代理時,指向接口和代理類proxy,cglib代理時 指向接口和子類(不使用proxy)
5.target 指向接口和子類
/**
* 此處需要注意的是,如果配置設置proxyTargetClass=false,或默認爲false,則是用JDK代理,否則使用的是CGLIB代理
* JDK代理的實現方式是基於接口實現,代理類繼承Proxy,實現接口。
* 而CGLIB繼承被代理的類來實現。
* 所以使用target會保證目標不變,關聯對象不會受到這個設置的影響。
* 但是使用this對象時,會根據該選項的設置,判斷是否能找到對象。
*/
@Pointcut("target(com.chenss.dao.IndexDaoImpl)")//目標對象,也就是被代理的對象。限制目標對象爲com.chenss.dao.IndexDaoImpl類
@Pointcut("this(com.chenss.dao.IndexDaoImpl)")//當前對象,也就是代理對象,代理對象時通過代理目標對象的方式獲取新的對象,與原值並非一個
@Pointcut("@target(com.chenss.anno.Chenss)")//具有@Chenss的目標對象中的任意方法
@Pointcut("@within(com.chenss.anno.Chenss)")//等同於@targ
這個比較難.......
proxy模式裏面有兩個重要的術語
proxy Class
target Class
CGLIB和JDK有區別 JDK是基於接口 cglib是基於繼承所有this可以在cglib作用
6.@annotation
這個很簡單........
作用方法級別
上述所有表達式都有@ 比如@Target(裏面是一個註解類xx,表示所有加了xx註解的類,和包名無關)
注意:上述所有的表達式可以混合使用,|| && !
@Pointcut("@annotation(com.chenss.anno.Chenss)")//匹配帶有com.chenss.anno.Chenss註解的方法
四、Spring AOP XML實現方式的注意事項
- 在aop:config中定義切面邏輯,允許重複出現,重複多次,以最後出現的邏輯爲準,但是次數以出現的次數爲準。
- aop:aspect ID重複不影響正常運行,依然能夠有正確結果。
- aop:pointcut ID重複會出現覆蓋,以最後出現的爲準。不同aop:aspect內出現的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">
<!-- 定義開始進行註解掃描 -->
<context:component-scan base-package="com.chenss"></context:component-scan>
<!-- 定義AspectJ對象使用的邏輯類,類中提供切面之後執行的邏輯方法 -->
<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中定義切面邏輯,允許重複出現,重複多次,以最後出現的邏輯爲準,但是次數以出現的次數爲準-->
<aop:config>
<!-- aop:aspect ID重複不影響正常運行,依然能夠有正確結果 -->
<!-- aop:pointcut ID重複會出現覆蓋,以最後出現的爲準。不同aop:aspect內出現的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>