靜態代理與動態代理
技術標籤:java
靜態代理
靜態代理就是我們自己手寫代理類; aspectJ靜態代理(編譯期生成代理類)
靜態代理可以實現在不修改目標物件程式碼的前提下,對目標物件的功能進行擴充套件;
靜態代理:(目標介面、目標介面實現、代理類)
優點:可以實現不對目標物件進行修改的前提下,對目標物件進行功能的擴充套件和增強,也就是擴充套件原功能,不汙染原始碼。
缺點:因為代理物件,需要實現與目標物件一樣的介面,如果目標介面類繁多,也會導致代理類繁多,另外一旦目標介面增加新方法,則代理類也需要維護;
實現方式
/**
* 目標介面
*
*/
public interface TargetInterface {
public void sayHello(String name);
public void sayThanks(String name);
}
/**
* 目標介面的實現
*
*/
public class TargetInterfaceImpl implements TargetInterface {
@Override
public void sayHello(String name) {
System.out.println("sayHello, " + name);
}
@Override
public void sayThanks(String name) {
System.out.println("sayThanks, " + name);
}
}
/**
* 目標介面的代理類
*
*/
public class TargetProxy implements TargetInterface {
//持有目標介面的引用
private TargetInterface targetInterface;
public TargetProxy(TargetInterface targetInterface) {
this.targetInterface = targetInterface;
}
@Override
public void sayHello(String name) {
System.out.println("start..............");
//中間調目標介面的真正的實現
targetInterface.sayHello(name);
System.out.println("end..............");
}
@Override
public void sayThanks(String name) {
System.out.println("start..............");
//中間調目標介面的真正的實現
targetInterface.sayThanks(name);
System.out.println("end..............");
}
}
public class Test2 {
public static void main(String[] args) {
TargetProxy targetProxy = new TargetProxy(new TargetInterfaceImpl());
targetProxy.sayHello("張無忌");
System.out.println("---------------------------");
targetProxy.sayThanks("張三丰");
}
}
結果輸出
動態代理
JDK動態代理
動態代理,是指在執行期動態的為指定的類生成其代理類;
JDK動態代理會在程式執行時生成一個$Proxy0.Class,該class類在程式碼中看不到,在磁碟中也沒有儲存,是程式執行時候在jvm記憶體中生成的,所以我們感覺動態代理很抽象,要想儲存該class檔案,我們可以在程式碼中設定:
System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
這樣就會在執行時候將該class檔案儲存到我們磁碟上;
動態代理在執行時將介面中宣告的所有方法都轉移到一個集中的InvocationHandler.invoke()方法進行處理,這樣在介面方法數量比較多的時候,我們可以進行靈活處理,而靜態代理需要在每一個方法進行中轉;
動態代理是AOP思想的底層實現,當然JDK的動態代理,目標類必須實現某個介面,如果某個類沒有實現介面則不能生成代理物件;
實現方式
/**
* 目標介面
*
*/
public interface TargetInterface {
public void sayHi();
public void work();
}
/**
* 目標介面實現
*
*/
public class TargetInterfaceImpl implements TargetInterface {
public void sayHi() {
System.out.println("Hi, dynamic proxy. sayHi.");
}
public void work() {
System.out.println("Hi, dynamic proxy.work");
}
}
/**
* 實現jdk提供的InvocationHandler介面
*
* 實現該介面是為了實現jdk的動態代理
*
* 此類不是真正的代理類,真正的代理的類在jvm記憶體中,我們看不見摸不著的,這個真正的代理類名字一般是以$Proxy.
*
*/
public class TargetProxy implements InvocationHandler {
//持有目標介面的引用,動態代理為了適配各種目標型別,把引用使用Object
private Object target;
/**
* 使用構造方法對目標介面的引用實現初始化
*
* @param target
*/
public TargetProxy(Object target) {
this.target = target;
}
/**
* 獲取真正的代理類
*
* @param interfaces
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
public <T> T getProxy(Class interfaces) {
//1、jvm記憶體中生成一個class類;
//2、根據該class類反射建立一個代理物件 [email protected]
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] {interfaces},
this);
}
/**
* 覆蓋InvocationHandler介面的方法
* 該方法會對目標介面的方法進行攔截
*
* @param proxy 這個就是我們那個代理類,就是jdk生成的那個叫$Proxy.代理類
* @param method 就是目標介面的方法,比如 sayhi(), work()的反射物件Method;
* @param args 就是目標介面的方法,比如 sayhi(), work()的引數
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增強(通知)......");
//中間是呼叫目標介面的方法
Object result = method.invoke(target, args);
System.out.println("後置增強(通知)......");
return result;
}
}
Cglib動態代理
官方Github:https://github.com/cglib
CGLib (Code Generation Library) 是一個強大、高效能、高質量的程式碼生成庫,它可以在執行時擴充套件JAVA類並實現介面;
位元組碼生成庫是生成和轉換Java位元組碼的高階API,它被AOP、測試、資料訪問框架用來生成動態代理物件和攔截欄位訪問;
CGLib 比 Java 的 java.lang.reflect.Proxy 類更強大的地方在於它不僅可以接管介面類的方法,還可以接管普通類的方法,為JDK的動態代理提供了很好的補充,通常可以使用Java的動態代理建立代理,但當要代理的類沒有實現介面時,那麼CGLIB是一個更好的選擇;
在一些開源框架中都採用了cglib:
Hibernate
Spring
Guice
CGLib 的底層是Java位元組碼操作框架 —— ASM https://asm.ow2.io
CGLIB類庫結構
net.sf.cglib.core: 底層位元組碼處理類,他們大部分與ASM有關;
net.sf.cglib.transform: 編譯期或執行期類和類檔案的轉換
net.sf.cglib.proxy: 實現建立代理和方法攔截器的類
net.sf.cglib.reflect: 實現快速反射類
net.sf.cglib.util: 集合排序等工具類
net.sf.cglib.beans: javabean相關的工具類
CGLIB實現動態代理
使用CGLib實現動態代理,完全不受代理類必須實現介面的限制,CGLib底層採用ASM位元組碼生成框架,使用位元組碼技術生成代理類,在JDK8之前CGLib動態比JDK的動態代理(使用Java反射)效率要高。
唯一需要注意的是,如果被代理的類被final修飾,那麼它不可被繼承,即不可被代理,同樣,如果被代理的類中存在final修飾的方法,那麼該方法也不可被代理;
因為CGLib原理是動態生成被代理類的子類;
final類不能被繼承,final方法不能被複寫;
使用CGLIB,需要兩個jar包:
cglib-3.3.0.jar
asm-7.1.jar
cglib-nodep-3.3.0.jar:使用nodep包不需要關聯asm的jar包,jar包內部已經包含了asm的類.
cglib-3.3.0.jar:使用此jar包需要關聯asm的jar包,否則執行時報錯;
CGLib動態代理原理是什麼?
CGLIB原理: 動態生成一個要代理類的子類,子類重寫要代理的類的所有不是final的方法。在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯;
CGLIB底層:使用位元組碼處理框架ASM,來轉換位元組碼並生成新的類。但不推薦直接使用ASM編碼開發,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉,編碼比較複雜繁瑣;
CGLIB缺點:對於final類和final方法,無法進行代理;
JDK動態代理與CGLib代理的區別?
原理區別:
JDK動態代理是利用反射機制生成一個實現代理介面的類(這個類看不見摸不著,在jvm記憶體中有這個類),在呼叫具體方法前呼叫InvokeHandler來處理。核心是實現InvocationHandler介面,使用invoke()方法進行面向切面的處理,呼叫相應的攔截和處理;
而cglib動態代理是利用asm開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理,核心是實現MethodInterceptor介面,使用intercept()方法進行面向切面的處理,呼叫相應的攔截和處理;
各自侷限:
1、JDK的動態代理機制只能代理實現了介面的類,而沒有實現介面的類就不能實現JDK的動態代理。
2、CGLib的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為採用的是繼承,所以不能對final修飾的類進行代理。
JDK Proxy 的優勢:
最小化依賴關係,減少依賴意味著簡化開發和維護,JDK 本身的支援,可能比 cglib 更加可靠;
可以平滑進行 JDK 版本升級,而位元組碼類庫通常需要進行更新以保證在新版 Java 上能夠使用。
CGLib 的優勢:
從某種角度看,必須要求呼叫者實現介面是有些侵入性的實踐,CGLib 動態代理就沒有這種限制,只操作我們關心的類,而不必為其他相關類增加工作量,一個普通的類也可以實現代理;
實現方式
public interface TargetInterface {
public String sayHello(String name);
public String sayThanks(String name);
}
public class TargetInterfaceImpl implements TargetInterface {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
@Override
public String sayThanks(String name) {
return "Thanks, " + name;
}
}
/**
* TargetProxy類還不是一個真正的代理類,它是代理類的一部分
*
*/
public class TargetProxy implements MethodInterceptor {
/**
* 獲取真正的代理類
*
* //1、jvm記憶體中生成一個class類;
* //2、根據該class類反射建立一個代理物件 [email protected]
* return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
* new Class<?>[] {interfaces},
* this);
*
* @param clazz
* @param <T>
* @return
*/
public <T> T getProxy(Class<T> clazz) {
//位元組碼增強的一個類
Enhancer enhancer = new Enhancer();
//設定父類
enhancer.setSuperclass(clazz);
//enhancer.setInterfaces(new Class[] {clazz});
//設定回撥類
enhancer.setCallback(this);
//建立代理類
return (T)enhancer.create();
}
/**
* 既可以 sayHello,也可以攔截 sayThanks
*
* @param obj
* @param method
* @param args
* @param proxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(method.getName() + "資料快取start..........");
//呼叫目標方法
Object result = proxy.invokeSuper(obj, args);
//就像mybatis一樣, 需要自己實現介面
//System.out.println("sayHello...................");
System.out.println(method.getName() + "資料快取end..........");
return result;
}
}