本文目標:帶領大家閱讀aop的原始碼,深入理解aop的原理,內容有點長,消化需要大概一周時間,做好準備,
Aop原理介紹
介紹aop相關的一些類
通過原始碼詳解aop代理的創建程序
通過原始碼詳解aop代理的呼叫程序
Aop代理一些特性的使用案例
最新2020整理收集的一些面試題(都整理成檔案),有很多干貨,包含mysql,netty,spring,執行緒,spring cloud等詳細講解,也有詳細的學習規劃圖,面試題整理等,我感覺在面試這塊講的非常清楚:獲取面試資料只需:[點擊這里領取!!!] 暗號:CSDN
Spring AOP原理
原理比較簡單,主要就是使用jdk動態代理和cglib代理來創建代理物件,通過代理物件來訪問目標物件,而代理物件中融入了增強的代碼,最終起到對目標物件增強的效果,
aop相關的一些類
連接點(JoinPoint)相關類
通知(Advice)相關的類
切入點(Pointcut)相關的類
切面(Advisor)相關的類
連接點(JoinPoint)相關類
JoinPoint介面
這個介面表示一個通用的運行時連接點(在AOP術語中)
package org.aopalliance.intercept;
public interface Joinpoint {
/**
* 轉到攔截器鏈中的下一個攔截器
*/
Object proceed() throws Throwable;
/**
* 回傳保存當前連接點靜態部分【的物件】,這里一般指被代理的目標物件
*/
Object getThis();
/**
* 回傳此靜態連接點 一般就為當前的Method(至少目前的唯一實作是MethodInvocation,所以連接點得靜態部分肯定就是本方法)
*/
AccessibleObject getStaticPart();
}
幾個重要的子介面和實作類,如下:
Invocation介面
此介面表示程式中的呼叫,呼叫是一個連接點,可以被攔截器攔截,
**
package org.aopalliance.intercept;
/**
* 此介面表示程式中的呼叫
* 呼叫是一個連接點,可以被攔截器攔截,
*/
public interface Invocation extends Joinpoint {
/**
* 將引數作為陣列物件獲取,可以更改此陣列中的元素值以更改引數,
* 通常用來獲取呼叫目標方法的引數
*/
Object[] getArguments();
}
**
MethodInvocation介面
用來表示連接點中方法的呼叫,可以獲取呼叫程序中的目標方法,
package org.aopalliance.intercept;
import java.lang.reflect.Method;
/
- 方法呼叫的描述,在方法呼叫時提供給攔截器,
- 方法呼叫是一個連接點,可以被方法攔截器攔截,
/
public interface MethodInvocation extends Invocation {
/*- 回傳正在被呼叫得方法~~~ 回傳的是當前Method物件,
- 此時,效果同父類的AccessibleObject getStaticPart() 這個方法
/
Method getMethod();
}*
ProxyMethodInvocation介面
表示代理方法的呼叫
public interface ProxyMethodInvocation extends MethodInvocation {
/**
* 獲取被呼叫的代理物件
*/
Object getProxy();
/**
* 克隆一個方法呼叫器MethodInvocation
*/
MethodInvocation invocableClone();
/**
* 克隆一個方法呼叫器MethodInvocation,并為方法呼叫器指定引數
*/
MethodInvocation invocableClone(Object... arguments);
/**
* 設定要用于此鏈中任何通知的后續呼叫的引數,
*/
void setArguments(Object... arguments);
/**
* 添加一些擴展用戶屬性,這些屬性不在AOP框架內使用,它們只是作為呼叫物件的一部分保留,用于特殊的攔截器,
*/
void setUserAttribute(String key, @Nullable Object value);
/**
* 根據key獲取對應的用戶屬性
*/
@Nullable
Object getUserAttribute(String key);
}
通俗點理解:連接點表示方法的呼叫程序,內部包含了方法呼叫程序中的所有資訊,比如被呼叫的方法、目標、代理物件、執行攔截器鏈等資訊,
上面定義都是一些介面,最終有2個實作,
ReflectiveMethodInvocation
當代理物件是采用jdk動態代理創建的,通過代理物件來訪問目標物件的方法的時,最終程序是由ReflectiveMethodInvocation來處理的,內部會通過遞回呼叫方法攔截器,最侄訓呼叫到目標方法,
CglibMethodInvocation
功能和上面的類似,當代理物件是采用cglib創建的,通過代理物件來訪問目標物件的方法的時,最終程序是由CglibMethodInvocation來處理的,內部會通過遞回呼叫方法攔截器,最侄訓呼叫到目標方法,
這2個類原始碼稍后詳解,
通知相關的類
通知用來定義需要增強的邏輯,

Advice介面
通知的底層介面
package org.aopalliance.aop;
public interface Advice {
}
BeforeAdvice介面
方法前置通知,內部空的
package org.springframework.aop;
public interface BeforeAdvice extends Advice {
}
Interceptor介面
此介面表示通用攔截器
package org.aopalliance.intercept;
public interface Interceptor extends Advice {
}
MethodInterceptor介面
方法攔截器,所有的通知均需要轉換為MethodInterceptor型別的,最終多個MethodInterceptor組成一個方法攔截器連,
package org.aopalliance.intercept;
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
/**
* 攔截目標方法的執行,可以在這個方法內部實作需要增強的邏輯,以及主動呼叫目標方法
*/
Object invoke(MethodInvocation invocation) throws Throwable;
}
AfterAdvice介面
后置通知的公共標記介面
package org.springframework.aop;
public interface AfterAdvice extends Advice {
}
MethodBeforeAdvice介面
方法執行前通知,需要在目標方法執行前執行一些邏輯的,可以通過這個實作,
通俗點說:需要在目標方法執行之前增強一些邏輯,可以通過這個介面來實作,before方法:在呼叫給定方法之前回呼,
package org.springframework.aop;
public interface MethodBeforeAdvice extends BeforeAdvice {
/**
* 呼叫目標方法之前會先呼叫這個before方法
* method:需要執行的目標方法
* args:目標方法的引數
* target:目標物件
*/
void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}
如同
public Object invoke(){
呼叫MethodBeforeAdvice#before方法
return 呼叫目標方法;
}
AfterReturningAdvice介面
方法執行后通知,需要在目標方法執行之后執行增強一些邏輯的,可以通過這個實作,
不過需要注意一點:目標方法正常執行后,才會回呼這個介面,當目標方法有例外,那么這通知會被跳過,
package org.springframework.aop;
public interface AfterReturningAdvice extends AfterAdvice {
/**
* 目標方法執行之后會回呼這個方法
* method:需要執行的目標方法
* args:目標方法的引數
* target:目標物件
*/
void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
}
如同
public Object invoke(){
Object retVal = 呼叫目標方法;
呼叫AfterReturningAdvice#afterReturning方法
return retVal;
}
ThrowsAdvice介面
package org.springframework.aop;
public interface ThrowsAdvice extends AfterAdvice {
}
此介面上沒有任何方法,因為方法由反射呼叫,實作類必須實作以下形式的方法,前3個引數是可選的,最后一個引數為需要匹配的例外的型別,
void afterThrowing([Method, args, target], ThrowableSubclass);
有效方法的一些例子如下:
public void afterThrowing(Exception ex)
public void afterThrowing(RemoteException)
public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)
通知包裝器
負責將各種非MethodInterceptor型別的通知(Advice)包裝為MethodInterceptor型別,
剛才有說過:Aop中所有的Advice最終都會轉換為MethodInterceptor型別的,組成一個方法呼叫鏈,然后執行
3個包裝器類
```java
MethodBeforeAdviceInterceptor
AfterReturningAdviceInterceptor
ThrowsAdviceInterceptor
MethodBeforeAdviceInterceptor類
這個類實作了MethodInterceptor介面,負責將MethodBeforeAdvice方法前置通知包裝為MethodInterceptor型別,創建這個型別的物件的時候需要傳遞一個MethodBeforeAdvice型別的引數,重點是invoke方法
```java
package org.springframework.aop.framework.adapter;
@SuppressWarnings("serial")
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
private final MethodBeforeAdvice advice;
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
//負責呼叫前置通知的方法
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
//繼續執行方法呼叫鏈
return mi.proceed();
}
}
AfterReturningAdviceInterceptor類
這個類實作了MethodInterceptor介面,負責將AfterReturningAdvice方法后置通知包裝為MethodInterceptor型別,創建這個型別的物件的時候需要傳遞一個AfterReturningAdvice型別的引數,重點是invoke方法
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
private final AfterReturningAdvice advice;
public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
//先執行方法呼叫鏈,可以獲取目標方法的執行結果
Object retVal = mi.proceed();
//執行后置通知
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
//回傳結果
return retVal;
}
}
ThrowsAdviceInterceptor類
這個類實作了MethodInterceptor介面,負責將ThrowsAdvice例外通知包裝為MethodInterceptor型別,創建這個型別的物件的時候需要傳遞一個Object型別的引數,通常這個引數是ThrowsAdvice型別的,重點是invoke方法
package org.springframework.aop.framework.adapter;
public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
private static final String AFTER_THROWING = "afterThrowing";
private final Object throwsAdvice;
//創建ThrowsAdviceInterceptor
public ThrowsAdviceInterceptor(Object throwsAdvice) {
Assert.notNull(throwsAdvice, "Advice must not be null");
this.throwsAdvice = throwsAdvice;
//獲取例外通知中定義的所有方法(public、默認的、protected、private)
Method[] methods = throwsAdvice.getClass().getMethods();
//輪詢methods
for (Method method : methods) {
//方法名稱為afterThrowing && 方法引數為1或者4
if (method.getName().equals(AFTER_THROWING) &&
(method.getParameterCount() == 1 || method.getParameterCount() == 4)) {
//獲取方法的最后一個引數型別
Class<?> throwableParam = method.getParameterTypes()[method.getParameterCount() - 1];
//判斷方法引數型別是不是Throwable型別的
if (Throwable.class.isAssignableFrom(throwableParam)) {
// 快取例外處理方法到map中(例外型別->例外處理方法)
this.exceptionHandlerMap.put(throwableParam, method);
}
}
}
//如果exceptionHandlerMap,拋出例外,所以最少要有一個例外處理方法
if (this.exceptionHandlerMap.isEmpty()) {
throw new IllegalArgumentException(
"At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
}
}
/**
* 獲取例外通知中自定義的處理例外方法的數量
*/
public int getHandlerMethodCount() {
return this.exceptionHandlerMap.size();
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
//呼叫通知鏈
return mi.proceed();
}
catch (Throwable ex) {
//獲取例外通知中自定義的處理例外的方法
Method handlerMethod = getExceptionHandler(ex);
//當處理的方法不為空
if (handlerMethod != null) {
//呼叫例外處理方法
invokeHandlerMethod(mi, ex, handlerMethod);
}
//繼續向外拋出例外
throw ex; //@1
}
}
/**
* 獲取throwsAdvice中處理exception引數指定的例外的方法
*/
@Nullable
private Method getExceptionHandler(Throwable exception) {
//獲取例外型別
Class<?> exceptionClass = exception.getClass();
//從快取中獲取例外型別對應的方法
Method handler = this.exceptionHandlerMap.get(exceptionClass);
//來一個回圈,查詢處理方法,回圈條件:方法為空 && 例外型別!=Throwable
while (handler == null && exceptionClass != Throwable.class) {
//獲取例外的父型別
exceptionClass = exceptionClass.getSuperclass();
//從快取中查找例外對應的處理方法
handler = this.exceptionHandlerMap.get(exceptionClass);
}
//將查找結果回傳
return handler;
}
//通過反射呼叫例外通知中的例外方法
private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable {
//構建方法請求引數
Object[] handlerArgs;
//若只有1個引數,引數為:例外物件
if (method.getParameterCount() == 1) {
handlerArgs = new Object[] {ex};
}
else {
//4個引數(方法、方法請求引數、目標物件、例外物件)
handlerArgs = new Object[] {mi.getMethod(), mi.getArguments(), mi.getThis(), ex};
}
try {
//通過反射呼叫例外通知中的方法
method.invoke(this.throwsAdvice, handlerArgs);
}
catch (InvocationTargetException targetEx) {
throw targetEx.getTargetException();
}
}
}
從上面可以看出,例外通知,自定義處理例外的方法有幾個特點
方法名稱必須為afterThrowing
方法引數必須1個或4個,最后一個引數是Throwable型別或其子型別
可以在例外處理中記錄一些例外資訊,這個還是比較有用的,但是注意一點目標方法拋出的例外最后還是會向外繼續拋出@1
光講原始碼,大家看著枯燥乏味,來點案例,
先來一個類,用來模擬用戶資金操作:充值、提現、查詢資金余額;提現的時候余額不足的時候,會拋出例外,
package com.javacode2018.aop.demo4;
//模擬資金操作
public class FundsService {
//賬戶余額
private double balance = 1000;
//模擬提現
double recharge(String userName, double price) {
System.out.println(String.format("%s提現%s", userName, price));
balance += price;
return balance;
}
//模擬提現
double cashOut(String userName, double price) {
if (balance < price) {
throw new RuntimeException("余額不足!");
}
System.out.println(String.format("%s提現%s", userName, price));
balance -= price;
return balance;
}
//獲取余額
double getBalance(String userName) {
return balance;
}
}
案例1:前置通知攔截非法訪問
資金操作的所有方法都需要驗證用戶名,當用戶名不是“路人”的時候,直接拋出非法訪問例外,
package com.javacode2018.aop.demo4;
import org.junit.Test;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
public class AopTest4 {
@Test
public void test1() {
//代理工廠
ProxyFactory proxyFactory = new ProxyFactory(new FundsService());
//添加一個方法前置通知,判斷用戶名不是“路人”的時候,拋出非法訪問例外
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
String userName = (String) args[0];
//如果不是路人的時候,拋出非法訪問例外
if (!"路人".equals(userName)) {
throw new RuntimeException(String.format("[%s]非法訪問!", userName));
}
}
});
//通過代理工廠創建代理
FundsService proxy = (FundsService) proxyFactory.getProxy();
//呼叫代理的方法
proxy.recharge("路人", 100);
proxy.recharge("張學友", 100);
}
}
運行輸出
路人提現100.0
java.lang.RuntimeException: [張學友]非法訪問!
at com.javacode2018.aop.demo4.AopTest4$1.before(AopTest4.java:25)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:55)
案例2:通過例外通知記錄例外
通過例外通知來捕獲所有方法的運行,發現例外之后,通知開發修復bug,
public static class SendMsgThrowsAdvice implements ThrowsAdvice {
//注意方法名稱必須為afterThrowing
public void afterThrowing(Method method, Object[] args, Object target, RuntimeException e) {
//監控到例外后發送訊息通知開發者
System.out.println("例外警報:");
System.out.println(String.format("method:[%s],args:[%s]", method.toGenericString(), Arrays.stream(args).collect(Collectors.toList())));
System.out.println(e.getMessage());
System.out.println("請盡快修復bug!");
}
}
@Test
public void test2() {
//代理工廠
ProxyFactory proxyFactory = new ProxyFactory(new FundsService());
//添加一個例外通知,發現例外之后發送訊息給開發者盡快修復bug
proxyFactory.addAdvice(new SendMsgThrowsAdvice());
//通過代理工廠創建代理
FundsService proxy = (FundsService) proxyFactory.getProxy();
//呼叫代理的方法
proxy.cashOut("路人", 2000);
}
運行輸出
例外警報:
method:[double com.javacode2018.aop.demo4.FundsService.cashOut(java.lang.String,double)],args:[[路人, 2000.0]]
余額不足!
請盡快修復bug!
java.lang.RuntimeException: 余額不足!
at com.javacode2018.aop.demo4.FundsService.cashOut(FundsService.java:18)
切入點(PointCut)相關類
通知(Advice)用來指定需要增強的邏輯,但是哪些類的哪些方法中需要使用這些通知呢?這個就是通過切入點來配置的,
代理創建程序原始碼決議
先看一段代碼
//代理工廠
ProxyFactory proxyFactory = new ProxyFactory(new FundsService());
//添加一個方法前置通知,判斷用戶名不是“路人”的時候,拋出非法訪問例外
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
String userName = (String) args[0];
//如果不是路人的時候,拋出非法訪問例外
if (!"路人".equals(userName)) {
throw new RuntimeException(String.format("[%s]非法訪問!", userName));
}
}
});
//通過代理工廠創建代理
FundsService proxy = (FundsService) proxyFactory.getProxy();
我們將上面代碼拆分一下,變成下面這樣
//1.創建代理所需引數配置(如:采用什么方式的代理、通知串列等)
AdvisedSupport advisedSupport = new AdvisedSupport();
//如:添加一個前置通知
advisedSupport.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
String userName = (String) args[0];
//如果不是路人的時候,拋出非法訪問例外
if (!"路人".equals(userName)) {
throw new RuntimeException(String.format("[%s]非法訪問!", userName));
}
}
});
//設定被代理的目標物件
FundsService target = new FundsService();
advisedSupport.setTarget(target);
//2.根據配置資訊獲取AopProxy物件,AopProxy用來負責創建最終的代理物件
// AopProxy介面有2個實作類(JDK動態代理、cglib代理)
// 具體最侄訓使用哪種方式,需要根據AdvisedSupport中指定的引數來判斷
// 創建AopProxy使用了簡單工廠模式
AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();
//通過AopProxy工廠獲取AopProxy物件
AopProxy aopProxy = aopProxyFactory.createAopProxy(advisedSupport);
//3.通過AopProxy創建代理物件
Object proxy = aopProxy.getProxy();
從上面可以看出創建代理有3個步驟,
創建代理3大步驟
創建代理所需引數配置
根據代理引數獲取AopProxy物件
通過AopProxy獲取代理物件
根據代理引數獲取AopProxy物件
TargetClassAware介面
比較簡單的一個介面,定義了一個方法,用來獲取目標物件型別,
所謂目標物件:就是被代理物件,比如上面的fundsService物件,
package org.springframework.aop;
public interface TargetClassAware {
@Nullable
Class<?> getTargetClass();
}
ProxyConfig類
這個類比較關鍵了,代理配置類,內部包含了創建代理時需要配置的各種引數,
package org.springframework.aop.framework;
/**
* 對外提供統一的代理引數配置類,以確保所有代理創建程式具有一致的屬性
*/
public class ProxyConfig implements Serializable {
// 標記是否直接對目標類進行代理,而不是通過介面產生代理
private boolean proxyTargetClass = false;
// 標記是否對代理進行優化,啟動優化通常意味著在代理物件被創建后,增強的修改將不會生效,因此默認值為false,
// 如果exposeProxy設定為true,即使optimize為true也會被忽略,
private boolean optimize = false;
// 標記是否需要阻止通過該配置創建的代理物件轉換為Advised型別,默認值為false,表示代理物件可以被轉換為Advised型別
boolean opaque = false;
// 標記代理物件是否應該被aop框架通過AopContext以ThreadLocal的形式暴露出去,
// 當一個代理物件需要呼叫它自己的另外一個代理方法時,這個屬性將非常有用,默認是是false,以避免不必要的攔截,
boolean exposeProxy = false;
// 標記該配置是否需要被凍結,如果被凍結,將不可以修改增強的配置,
// 當我們不希望呼叫方修改轉換成Advised物件之后的代理物件時,這個配置將非常有用,
private boolean frozen = false;
//省略了屬性的get set方法
}
最新2020整理收集的一些面試題(都整理成檔案),有很多干貨,包含mysql,netty,spring,執行緒,spring cloud等詳細講解,也有詳細的學習規劃圖,面試題整理等,我感覺在面試這塊講的非常清楚:獲取面試資料只需:[點擊這里領取!!!] 暗號:CSDN
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/210423.html
標籤:其他
