Spring原理與原始碼分析系列(六)- Spring AOP入門與概述
一、AOP
1、什麼是AOP
AOP :Aspect-Oriented Programming,面向切面程式設計的簡稱。
在我們的專案程式碼中,有大量與日誌、事務、許可權(AOP稱之為橫切關注點)相關的程式碼鑲嵌在業務程式碼當中,造成大量程式碼的重複與程式碼的冗餘。
雖然可以將這些重複的程式碼封裝起來再進行呼叫,但是這樣的呼叫方式比較單一,不夠靈活,無法更好地以模組化的方式,對這些橫切關注點進行組織和實現。
AOP提出切面(Aspect)的概念,以模組化的方式對橫切關注點進行封裝,再通過織入的方式將切面織入到業務邏輯程式碼當中。這樣橫切關注點與業務邏輯程式碼分離,業務邏輯程式碼中就不再含有日誌、事務、許可權等程式碼的呼叫,可以很好的進行管理。
2、AOP基本概念
AOP中的相關術語有:Joinpoint,Pointcut,Advice,Aspect,Introduction,Weaving。
這些概念將在Spring AOP中詳細描述,此處略去。
(1)點(Joinpoint)
連線點是在應用執行過程中能夠插入切面的一個點。這個點可以是呼叫方法時、丟擲異常時、甚至修改一個欄位時。切面程式碼可以利用這些點插入到應用的正常流程之中,並新增行為。
(2)切點(Pointcut)
如果說通知定義了切面“是什麼”和“何時”的話,那麼切點就定義了“何處”。比如我想把日誌引入到某個具體的方法中,這個方法就是所謂的切點。
(3)通知(Advice)
在AOP中,切面的工作被稱為通知。通知定義了切面“是什麼”以及“何時”使用。除了描述切面要完成的工作,通知還解決了何時執行這個工作的問題。
Spring切面可以應用5種型別的通知:
- 前置通知(Before):在目標方法被呼叫之前呼叫通知功能;
- 後置通知(After):在目標方法完成之後呼叫通知,此時不會關心方法的輸出是什麼;
- 返回通知(After-returning):在目標方法成功執行之後呼叫通知;
- 異常通知(After-throwing):在目標方法丟擲異常後呼叫通知;
- 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法呼叫之前和呼叫之後執行自定義的行為;
(4)切面(Aspect)
切面是通知和切點的結合。通知和切點共同定義了切面的全部內容———他是什麼,在何時和何處完成其功能。
(5)引入(Introduction)
引入允許我們向現有的類新增新的方法和屬性(Spring提供了一個方法注入的功能)。
(6)織入(Weaving)
把切面應用到目標物件來建立新的代理物件的過程,織入一般發生在如下幾個時機:
- 編譯時:當一個類檔案被編譯時進行織入,這需要特殊的編譯器才可以做的到,例如AspectJ的織入編譯器
- 類載入時:使用特殊的ClassLoader在目標類被載入到程式之前增強類的位元組程式碼
- 執行時:切面在執行的某個時刻被織入,SpringAOP就是以這種方式織入切面的,原理應該是使用了JDK的動態代理技術。
(此處術語解釋來源:
https://www.jianshu.com/p/5155aabaec3f)
3、AOP的分類
AOP主要分為靜態AOP和動態AOP。
(1)靜態AOP
靜態AOP,也稱為第一代AOP,是指將相應橫切關注點以Aspect形式實現之後,在編譯階段,通過特定編譯器將Aspect織入到目標類當中。
優點:Aspect直接以Java位元組碼的形式編譯到Java Class類中,Java虛擬機器可以正常載入類,沒有效能損失。
缺點:不夠靈活,如果要修改織入的位置時,就需要修改Aspect和重新編譯。
靜態AOP需要有3個重要因素:
1)共同的介面:定義方法不提供實現,供外部呼叫;
2)實現類:上述介面具體實現;
3)代理類:注入的是實現類,呼叫介面的方法實際是呼叫實現類的方法的實現。
下面通過程式碼來看看靜態AOP是如何實現的。
//共同介面
public interface CommonService {
public void sayHello();
}
//介面的具體實現
public class CommonServiceImpl implements CommonService {
@Override
public void sayHello() {
System.out.println("hello, 靜態代理!");
}
}
//代理類
public class StaticProxy implements CommonService {
private CommonService realService;
public StaticProxy(CommonService realService){
this.realService = realService;
}
@Override
public void sayHello() {
//織入橫切邏輯
System.out.println("日誌輸出1.。。");
realService.sayHello();
//織入橫切邏輯
System.out.println("日誌輸出2.。。");
}
}
//測試
public static void main(String[] args) {
CommonService realService = new CommonServiceImpl();
StaticProxy proxy = new StaticProxy(realService);
proxy.sayHello();
}
輸出結果:
日誌輸出1.。。
hello, 靜態代理!
日誌輸出2.。。
可以看到,在執行前,即將橫切邏輯織入到所需要織入的位置。
我們為RealService類產生了一個代理物件StaticProxy,假如還有RealService2,RealService3….,就要繼續生成代理物件StaticProxy2,StaticProxy3,,,,,這樣比較麻煩,因此就需要動態AOP來實現。
(2)動態AOP
動態AOP,也稱為第二代AOP,是指在類載入或者系統執行階段,將Aspect程式碼動態織入到目標類當中。
與靜態AOP最大的不同之處在於織入過程發生在系統執行後,而不是預編譯階段。
優點:比較靈活,可以動態更改織入邏輯。
缺點:由於發生階段是在類載入或者系統執行階段,因此會造成執行時效能損失。
動態AOP可以通過JDK動態代理或者Cglib來實現,下面通過JDK動態代理的方式來實現動態AOP。
//同樣需要公共介面
public interface CommonService {
public void sayHello();
}
//具體實現類
public class CommonServiceImpl implements CommonService {
@Override
public void sayHello() {
System.out.println("hello, 動態代理!");
}
}
//動態代理類
public class DynamicProxy implements InvocationHandler {
//要代理的物件
private Object obj;
//實際注入的物件
public DynamicProxy(Object realObj){
this.obj = realObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日誌輸出1.....");
method.invoke(obj, args);
System.out.println("日誌輸出1.....");
return null;
}
}
//測試:通過公共介面的ClassLoader和代理類生成一個代理類物件
public static void main(String[] args) {
//產生一個代理類
InvocationHandler handler = new DynamicProxy(new CommonServiceImpl());
//產生代理類物件
CommonService proxyService
= (CommonService) Proxy.newProxyInstance(CommonService.class.getClassLoader(),
new Class<?>[]{CommonService.class},
handler);
proxyService.sayHello();
}
輸出:
日誌輸出1…..
hello, 動態代理!
日誌輸出2…..
這裡有幾個需要注意的地方:
- InvocationHandler :InvocationHandler 用來生成動態代理類,它的構造方法注入了具體的實現類,表示被代理的物件;
invoke()方法的幾個引數
proxy:表示代理類物件本身,作用不大
method:正在被呼叫的方法;
args:方法的引數;Proxy.newProxyInstance
該方法是java.lang.reflect包下Proxy類的靜態方法,方法定義如下:
public static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
其中:loader:表示被代理物件的類載入器;
interfaces:代理類要實現的介面列表,只能是介面型別;
h:h的型別為InvocationHandler,它是一個介面,也定義在java.lang.reflect包中,它只定義了一個方法invoke,對代理介面所有方法的呼叫都會轉給該方法
4、AOP實現機制
Java平臺主要提供瞭如下兩種方式實現AOP:
- 動態代理(Dynamic Proxy)—需要有介面
- 動態位元組碼增強(Cglib)—不需要介面
(1)動態代理(Dynamic Proxy)
JDK1.3之後,引入了動態代理(Dynamic Proxy)機制。
在執行期間為相應介面動態生成對應的代理物件,再將橫切關注點邏輯封裝到動態代理的InvocationHandler中。在系統執行期間,將橫切邏輯織入到代理類指定位置中。
缺點:動態代理只針對介面有效。
在上節動態AOP中我們已經看到了如何使用JDK動態代理的方式去實現AOP,本節不再贅述。
(2)動態位元組碼增強(Cglib)
有的時候我們無法生成介面,自然就無法使用動態代理的方式,這個時候我們就可以使用ASM(位元組碼增強框架)或者Cglib技術來動態生成位元組碼的Class檔案,而只要符合JVM規範的.class檔案都可以被JVM所載入。
Cglib就是在系統執行期間,通過動態位元組碼增強技術,為需要織入橫切邏輯的目標類生成對應子類,代理類重寫了父類所有public非final的方法(即橫切邏輯加到方法當中)。
當程式呼叫目標類時,通過攔截方法實際最後執行的是具有橫切邏輯的子類。
缺點:如果目標類或目標類中的方法是final的話,就無法進行織入(final類不能被繼承,final方法不能被重寫)。
下面通過程式碼來看看動態位元組碼增強(Cglib)是如何實現AOP的。
//需要織入橫切邏輯的目標類
public class Login {
public void login(String username){
if ("admin".equals(username)){
System.out.println("登入成功!");
}else {
System.out.println("登入失敗!");
}
}
}
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//織入橫切邏輯
System.out.println("許可權檢查1,,,");
//呼叫目標類方法
Object result = methodProxy.invokeSuper(o, objects);
//織入橫切邏輯
System.out.println("許可權檢查2,,,");
return result;
}
}
public class CglibTest {
// 生成代理物件
private static<T> T getProxy(Class<T> clazz){
Enhancer enhancer = new Enhancer();
//將目標類設定為父類
enhancer.setSuperclass(clazz);
//設定回撥
enhancer.setCallback(new MyMethodInterceptor());
return (T) enhancer.create();
}
public static void main(String[] args) {
Login loginProxy = getProxy(Login.class);
loginProxy.login("admin");
}
}
輸出結果:
許可權檢查1,,,
登入成功!
許可權檢查2,,,
可以看到,首先重寫MethodInterceptor介面中的intercept方法,在該方法中完成橫切邏輯的織入以及目標類的方法的呼叫,
然後通過Enhancer類生成代理物件,將目標類設定為代理類,並完成回撥
AOP的內容就介紹到此,下面將介紹Spring AOP的相關理論和實戰。
2018/03/20 in NJ.
相關文章
- Spring原始碼剖析6:Spring AOP概述Spring原始碼
- 原始碼解析Spring AOP的載入與生效原始碼Spring
- Spring AOP之原始碼分析Spring原始碼
- Spring AOP 原理原始碼深度剖析Spring原始碼
- Spring Security原始碼分析六:Spring Social社交登入原始碼解析Spring原始碼
- 5.1 Spring5原始碼--Spring AOP原始碼分析一Spring原始碼
- 5.2 Spring5原始碼--Spring AOP原始碼分析二Spring原始碼
- 5.2 spring5原始碼--spring AOP原始碼分析三---切面原始碼分析Spring原始碼
- Spring AOP高階應用與原始碼剖析Spring原始碼
- Spring IOC/AOP原理極簡概念入門Spring
- Spring系列.AOP原理簡析Spring
- Spring原始碼分析:BeanPostProcessor原理Spring原始碼Bean
- Spring原始碼系列(四)--spring-aop是如何設計的Spring原始碼
- Spring5.0原始碼學習系列之Spring AOP簡述Spring原始碼
- Spring系列之AOP基本主要類概述Spring
- 5.2 spring5原始碼--spring AOP原始碼分析二--切面的配置方式Spring原始碼
- Spring AOP 原始碼解析Spring原始碼
- Spring Security原始碼分析十四:Spring Social社交登入繫結與解綁Spring原始碼
- Spring AOP從零單排-織入時期原始碼分析Spring原始碼
- Spring事物入門簡介及AOP陷阱分析Spring
- spring原始碼分析第二天------spring系統概述以及IOC實現原理Spring原始碼
- spring入門aop和iocSpring
- Spring原始碼分析之AOP從解析到呼叫Spring原始碼
- 理解Spring(二):AOP 的概念與實現原理Spring
- Spring原始碼剖析7:AOP實現原理詳解Spring原始碼
- spring原始碼解讀-aopSpring原始碼
- Spring AOP 原始碼初窺(三)掃描Advice與Bean匹配Spring原始碼Bean
- Spring原始碼分析——搭建spring原始碼Spring原始碼
- Spring AOP基礎、快速入門Spring
- Spring Ioc原始碼分析系列--@Autowired註解的實現原理Spring原始碼
- 淺嘗Spring註解開發_AOP原理及完整過程分析(原始碼)Spring原始碼
- Spring框架系列(10) - Spring AOP實現原理詳解之AOP代理的建立Spring框架
- Spring原始碼剖析8:Spring事務概述Spring原始碼
- Spring系列之初識Spring Spring概述Spring
- Spring原始碼系列(三)--spring-aop的基礎元件、架構和使用Spring原始碼元件架構
- Spring原始碼系列(二)--bean元件的原始碼分析Spring原始碼Bean元件
- Spring原始碼系列:BeanDefinition載入(下)Spring原始碼Bean
- 【spring原始碼】六、@EnableAspectJAutoProxySpring原始碼