動態代理與Spring AOP簡單介紹
代理模式
定義:給某個物件提供一個代理物件,並由代理物件控制對於原物件的訪問,即客戶不直接操控原物件,而是通過代理物件間接地操控原物件。
代理的實現方式
-
靜態代理:代理類是在編譯時就實現好的,Java 編譯完成後代理類是一個實際的 class 檔案。
-
動態代理:代理類是在執行時生成的,Java 編譯完之後並沒有實際的 class 檔案,而是在執行時動態生成的類位元組碼,並載入到JVM中。
JDK動態代理 (基於介面代理)
Java實現動態代理的大致步驟如下:
-
定義一個委託類和公共介面。
-
自己定義一個類(呼叫處理器類,即實現
InvocationHandler
介面),這個類的目的是指定執行時將生成的代理類需要完成的具體任務(包括Preprocess和Postprocess),即代理類呼叫任何方法都會經過這個呼叫處理器類。 -
生成代理物件,需要指定委託物件實現的一系列介面呼叫處理器類的例項。
Java 實現動態代理主要涉及以下幾個類:
-
java.lang.reflect.Proxy
: 這是生成代理類的主類,通過 Proxy 類生成的代理類都繼承了 Proxy 類,即DynamicProxyClass extends Proxy
。 -
java.lang.reflect.InvocationHandler
Java 實現動態代理的缺點:因為 Java 的單繼承特性(每個代理類都繼承了 Proxy 類),只能針對介面建立代理類,不能針對類建立代理類。
案例分析
public class DynamicProxyDemo { public static void main(String[] args) { Subject realSubject = new RealSubject(); //1.建立委託物件 ProxyHandler handler = new ProxyHandler(realSubject); //2.建立呼叫處理器物件 Subject proxySubject = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), handler); //3.動態生成代理物件 proxySubject.request(); //4.通過代理物件呼叫方法 } } /** * 介面 */ interface Subject{ void request(); } /** * 委託類 */ class RealSubject implements Subject{ public void request(){ System.out.println("====RealSubject Request===="); } } /** * 代理類的呼叫處理器 */ class ProxyHandler implements InvocationHandler{ private Subject subject; public ProxyHandler(Subject subject){ this.subject = subject; } /** *這個函式是在代理物件呼叫任何一個方法時都會呼叫的,方法不同會導致第二個引數method不同,第一個 *引數是代理物件(表示哪個代理物件呼叫了method方法),第二個引數是 Method 物件(表示哪個方法 *被呼叫了),第三個引數是指定呼叫方法的引數。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("====before====");//定義預處理的工作,當然你也可以根據 method 的不同進行不同的預處理工作 Object result = method.invoke(subject, args); System.out.println("====after===="); return result; } }
Cglib代理(基於繼承代理)
-
JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現.
-
Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
-
Cglib包的底層是通過使用一個小而塊的位元組碼處理框架ASM來轉換位元組碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉.
Cglib子類代理實現方法:
1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接引入spring-core-3.2.5.jar
即可.
2.引入功能包後,就可以在記憶體中動態構建子類
3.代理的類不能為final,否則報錯
4.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法 ,如果方法為static,private則無法進行代理。
案例分析
public class UserDao {
public void save() {
System.out.println("----已經儲存資料!----");
}
}
/**
* Cglib子類代理工廠
* 對UserDao在記憶體中動態構建一個子類物件
*/
public class ProxyFactory implements MethodInterceptor{
//維護目標物件
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//給目標物件建立一個代理物件
public Object getProxyInstance(){
//1.工具類
Enhancer en = new Enhancer();
//2.設定父類
en.setSuperclass(target.getClass());
//3.設定回撥函式
en.setCallback(this);
//4.建立子類(代理物件)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("開始事務...");
//執行目標物件的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事務...");
return returnValue;
}
public static void main(String[] args){
UserDao userDao = new UserDao();
UserDao proxy=(UserDao)new ProxyFactory(userDao).getProxyInstance();
proxy.save();
}
}
Spring AOP
代理機制:
1、若目標物件實現了若干介面,spring使用JDK的java.lang.reflect.Proxy類代理。
優點:因為有介面,所以使系統更加鬆耦合
缺點:為每一個目標類建立介面
2、若目標物件沒有實現任何介面,spring使用CGLIB庫生成目標物件的子類。
優點:因為代理類與目標類是繼承關係,所以不需要有介面的存在。
缺點:因為沒有使用介面,所以系統的耦合性沒有使用JDK的動態代理好。
Spring AOP原理
1、當spring容器啟動的時候,載入兩個bean,對像個bean進行例項化
2、當spring容器對配置檔案解析到<aop:config>的時候,把切入點表示式解析出來,按照切入點表示式匹配spring容器內容的bean
3、如果匹配成功,則為該bean建立代理物件
4、當客戶端利用context.getBean獲取一個物件時,如果該物件有代理物件,則返回代理物件,如果沒有代理物件,則返回物件本身
總結:
如果加入容器的目標物件有實現介面,用JDK代理
如果目標物件沒有實現介面,用Cglib代理
如果目標物件實現了介面,且強制使用cglib代理,則會使用cglib代理。
侷限:
spring aop的切入點支援有限,而且對於static方法和final方法都無法支援aop(因為此類方法無法生成代理類);另外spring aop只支援對於ioc容器管理的bean,其他的普通java類無法支援aop。現在的spring整合了aspectj,在spring體系中可以使用aspectj語法來實現aop。
AspectJ
AspectJ是一個java實現的AOP框架,它能夠對java程式碼進行AOP編譯(一般在編譯期進行),讓java程式碼具有AspectJ的AOP功能(當然需要特殊的編譯器),可以這樣說AspectJ是目前實現AOP框架中最成熟,功能最豐富的語言,更幸運的是,AspectJ與java程式完全相容,幾乎是無縫關聯。
織入方式及其原理概要
AspectJ應用到java程式碼的過程(這個過程稱為織入),對於織入這個概念,可以簡單理解為aspect(切面)應用到目標函式(類)的過程。對於這個過程,一般分為動態織入和靜態織入,動態織入的方式是在執行時動態將要增強的程式碼織入到目標類中,這樣往往是通過動態代理技術完成的,如Java JDK的動態代理(Proxy,底層通過反射實現)或者CGLIB的動態代理(底層通過繼承實現),Spring AOP採用的就是基於執行時增強的代理技術,ApectJ採用的就是靜態織入的方式。ApectJ主要採用的是編譯期織入,在這個期間使用AspectJ的acj編譯器(類似javac)把aspect類編譯成class位元組碼後,在java目標類編譯時織入,即先編譯aspect類再編譯目標類。
AOP 術語
- 通知:
- 定義:切面也需要完成工作。在 AOP 術語中,切面的工作被稱為通知。
- 工作內容:通知定義了切面是什麼以及何時使用。除了描述切面要完成的工作,通知還解決何時執行這個工作。
- Spring 切面可應用的 5 種通知型別:
- Before——在方法呼叫之前呼叫通知
- After——在方法完成之後呼叫通知,無論方法執行成功與否
- After-returning——在方法執行成功之後呼叫通知
- After-throwing——在方法丟擲異常後進行通知
- Around——通知包裹了被通知的方法,在被通知的方法呼叫之前和呼叫之後執行自定義的行為
- 連線點:
- 定義:連線點是一個應用執行過程中能夠插入一個切面的點。
- 連線點可以是呼叫方法時、丟擲異常時、甚至修改欄位時、
- 切面程式碼可以利用這些點插入到應用的正規流程中
- 程式執行過程中能夠應用通知的所有點。
- 切點:
- 定義:如果通知定義了“什麼”和“何時”。那麼切點就定義了“何處”。切點會匹配通知所要織入的一個或者多個連線點。
- 通常使用明確的類或者方法來指定這些切點。
- 作用:定義通知被應用的位置(在哪些連線點)
- 切面:
- 定義:切面是通知和切點的集合,通知和切點共同定義了切面的全部功能——它是什麼,在何時何處完成其功能。
- 引入:
- 引入允許我們向現有的類中新增方法或屬性
- 織入:
- 織入是將切面應用到目標物件來建立的代理物件過程。
- 切面在指定的連線點被織入到目標物件中,在目標物件的生命週期中有多個點可以織入
- 編譯期——切面在目標類編譯時期被織入,這種方式需要特殊編譯器。AspectJ的織入編譯器就是以這種方式織入切面。
- 類載入期——切面在類載入到JVM ,這種方式需要特殊的類載入器,他可以在目標類被引入應用之前增強該目標類的位元組碼。AspectJ5 的 LTW 就支援這種織入方式
- 執行期——切面在應用執行期間的某個時刻被織入。一般情況下,在織入切面時候,AOP 容器會為目標物件動態的建立代理物件。Spring AOP 就是以這種方式織入切面。