簡單瞭解靜態代理,JDK提供的動態代理和cglib的動態代理
靜態代理:
定義一個公共介面和公共方法,
package proxy;
public interface Work {
void sayHello();
String getName();
}
建立一個被代理類,實現公共介面和方法;
package proxy; public class People implements Work { @Override public void sayHello() { System.out.println("my name is A"); } @Override public String getName() { return "A"; } }
建立一個代理類,實現公共介面和方法,這樣它們就具有了相同的方法。
代理類中,需要持有一個被代理類物件,可以當做代理類的成員變數,在代理類的構造方法中傳入並賦值。
package proxy; public class StaticProxyPeople implements Work { People A = new People(); public StaticProxyPeople(People A) { this.A = A; } @Override public void sayHello() { System.out.println("代理方法開始:"); A.sayHello(); System.out.println("代理方法結束。"); } @Override public String getName() { String name = A.getName(); name = "代理處理過的name:" + name; return name; } }
此時代理類和被代理類擁有相同的方法,然後在代理類的方法中,可以呼叫被代理類的方法,達到操作被代理類的目的。而在呼叫方法前後,又可以定義自己的特殊邏輯。
package proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { People people = new People(); StaticProxyPeople staticProxyPeople = new StaticProxyPeople(people); staticProxyPeople.sayHello(); System.out.println(staticProxyPeople.getName()); } } 輸出結果: 代理方法開始: my name is A 代理方法結束。 代理處理過的name:A
這樣子當外界希望呼叫被代理類的方法時,可以建立一個代理類的物件,通過對代理類物件的方法的呼叫,達到操作被代理類物件的目的。
缺點:一個代理類,只能針對一個被代理類進行代理。
JDK實現的動態代理:
在java的動態代理機制中,有兩個重要的類或介面,一個是 InvocationHandler介面、另一個則是Proxyle類,這一個類和介面是實現我們動態代理所必須用到的。
1、InvocationHandler
舉例:
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class WrokHandler implements InvocationHandler {
private Object subject; // 這個就是我們要代理的真實物件,也就是真正執行業務邏輯的類
public WrokHandler(Object subject) {
this.subject = subject;
}
/**
* proxy: 指代JDK動態生成的最終代理物件
* method: 指代的是我們所要呼叫真實物件的某個方法的Method物件
* args: 指代的是呼叫真實物件某個方法時接受的引數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
System.out.println("呼叫方法:" + method.getName()+ "之前");
// 當代理物件呼叫真實物件的方法時,其會自動的跳轉到代理物件關聯的handler物件的invoke方法來進行呼叫
result = method.invoke(subject, args);// 需要指定被代理物件和傳入引數
System.out.println("返回值:" + result);
System.out.println("呼叫方法:" + method.getName()+ "之後");
return result;
}
}
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class SleepHandler implements InvocationHandler {
private Object subject; // 這個就是我們要代理的真實物件,也就是真正執行業務邏輯的類
public SleepHandler(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
System.out.println("SleepHandler.invoke 呼叫方法:" + method.getName() + "之前");
// 當代理物件呼叫真實物件的方法時,其會自動的跳轉到代理物件關聯的handler物件的invoke方法來進行呼叫
result = method.invoke(subject, args);// 需要指定被代理物件和傳入引數
System.out.println("SleepHandler.invoke 返回值:" + result);
System.out.println("SleepHandler.invoke 呼叫方法:" + method.getName() + "之後");
return result;
}
}
解析:
每一個代理類都必須要實現InvocationHandler這個介面,
同時,也要持有一個被代理類真實物件。
代理類實現了InvocationHandler介面後,會重寫invoke方法。
三個引數分別是:
proxy: 指代JDK動態生成的最終代理物件
method: 指代的是我們所要呼叫真實物件的某個方法的Method物件
args: 指代的是呼叫真實物件某個方法時接受的引數
而在invoke方法中,我們通過method.invoke(subject, args); 來呼叫真實被代理物件的方法。
簡單來說,就是當定義了以上WorkHandler後,對代理類物件方法的呼叫,都會由invoke方法來處理,才會傳達到被代理物件。
2.Proxy
舉例:
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
// 被代理的物件
People A = new People();
// 建立代理物件,代理物件持有一個被代理物件
InvocationHandler handler = new WrokHandler(A);
// 獲得被代理類的,類載入器Classloader
// 作用:
// 負責將 Class 載入到 JVM 中
// 審查每個類由誰載入(父優先的等級載入機制)
// 將 Class 位元組碼重新解析成 JVM 統一要求的物件格式
ClassLoader loader = A.getClass().getClassLoader();
// 獲得被代理類 實現的所有介面
Class[] interfaces = A.getClass().getInterfaces();
// 需要指定類裝載器、一組介面及呼叫處理器生成動態代理類例項
Work proxy = (Work) Proxy.newProxyInstance(loader, interfaces, handler);
System.out.println("動態代理物件的型別:" + proxy.getClass().getName());
proxy.sayHello();
System.out.print(proxy.getName());
InvocationHandler handler1 = new SleepHandler(A);
Sleep proxy1 = (Sleep) Proxy.newProxyInstance(loader, interfaces, handler1);
System.out.println("動態代理物件的型別:" + proxy.getClass().getName());
proxy1.goToSleep();
proxy1.getUp();
}
}
輸出結果:
動態代理物件的型別:$Proxy0
呼叫方法:sayHello之前
my name is A
返回值:null
呼叫方法:sayHello之後
呼叫方法:getName之前
返回值:A
呼叫方法:getName之後
A動態代理物件的型別:$Proxy0
SleepHandler.invoke 呼叫方法:goToSleep之前
i am go to sleep
SleepHandler.invoke 返回值:null
SleepHandler.invoke 呼叫方法:goToSleep之後
SleepHandler.invoke 呼叫方法:getUp之前
i am get up
SleepHandler.invoke 返回值:null
SleepHandler.invoke 呼叫方法:getUp之後
解析:
代理類同樣需要持有一個真實代理物件
當我們希望建立一個代理類例項時,需要呼叫 Proxy.newProxyInstance(loader, interfaces, handler);方法
三個引數:
loader 被代理物件的類載入器,這裡存有真實物件的父類,子類關係,和其他類的優先載入關係等等。
interfaces 被代理物件實現的所有介面,JDK提供的動態代理,說白了,就是讓代理類也去實現被代理類的所有介面,重寫被代理類的所有方法來實現的。
handler 我們自定義的代理類例項,它實現了InvocationHandler介面。用來處理Proxy中所有方法的呼叫
首先解釋一下JDK動態代理的大致流程,JDK主要會做以下工作:
- 建立一個被代理類的例項
- 通過被代理類的例項,獲取被代理類的載入器,和實現所有介面
- 呼叫Proxy.newProxyInstance()方法,需要傳入被代理類的載入資訊和它實現的介面資訊,還有一個handler例項,這個例項決定創建出的代理類例項,在代理過程中,走哪個InvocationHandler例項的invoke方法,傳入是的哪個handler例項,就走哪個例項的invoke方法。
- 同時Proxy類,也在程式碼中動態建立該代理類的位元組碼; 即代理類是在jvm的執行中建立的。 將對應的位元組碼轉換為對應的class 物件;
- Proxy 的class物件 以傳入的handler物件為引數,例項化一個代理類物件;
- 在接收例項化的代理類物件時,我們無法具體指導代理類物件的類名,只能將它向上造型為某個業務介面,即我們希望代理類例項,能代理哪些方法,我就讓它強轉為哪個介面型別。我們希望代理類例項,在代理過程中,執行哪些特定邏輯,我們在Proxy.newProxyInstance()方法中傳入哪個handler例項。
整理一下邏輯:
代理類例項建立之前,需要傳入一個自定義的handler例項,而自定義的handler類需要實現InvocationHandler介面,重寫invoke方法,在重寫的invoke方法中,增加自定義邏輯。
代理類例項建立時,是Proxy.newInstance(loader, interface[], handler)方法根據傳入的被代理類的類載入器資訊,實現的所有介面,在jvm中動態的生成位元組碼檔案和.class檔案。
代理類例項建立後,又需要強轉型別,向上造型為被代理類的某個介面型別,強制轉換的是被代理類實現的某個介面型別,就可以呼叫這個介面的方法。如果強轉的型別不是被代理類實現過的,就會有異常。
問題:
JDK是如何生成最終的代理類的,其實最根本的是通過動態生成代理類的位元組碼,然後通過這些位元組碼實現代理類的生成。
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces);
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
// 這裡動態生成代理類的位元組碼
final byte[] classFile = gen.generateClassFile();
// 如果saveGeneratedFiles的值為true,則會把所生成的代理類的位元組碼儲存到硬碟上
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
// 返回代理類的位元組碼
return classFile;
}
代理類例項是怎麼呼叫hanler的invoke方法的?我們來看一下代理類的class檔案
public final void hello(String paramString)
{
try
{
this.h.invoke(this, m3, new Object[] { paramString });
//就是呼叫我們自定義的InvocationHandlerImpl的 invoke方法:
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
注意這裡的this.h.invoke中的h,它是類Proxy中的一個屬性
protected InvocationHandler h;
因為這個代理類繼承了Proxy,所以也就繼承了這個屬性,而這個屬性值就是我們定義的
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
同時我們還發現,invoke方法的第一引數在底層呼叫的時候傳入的是this,也就是最終生成的代理物件ProxySubject,這是JVM自己動態生成的,而不是我們自己定義的代理物件。
JDK的動態代理通過ProxyGenerator.generateProxyClass()生成代理類的位元組碼,由Proxy.defineClass0()載入,可以看到這個代理類 extends Proxy implements Interface1, ... {...}。
而在代理類中,與Proxy有關的只有Proxy的變數protected InvocationHandler h;這個InvocationHandler例項的引用,在呼叫介面方法時實際呼叫的是super.h.invoke(this, method, args)。
缺點:使用JDK動態代理,目標類必須實現的某個介面,如果某個類沒有實現介面則不能生成代理物件。
CGLIB
(Code Generation Library)是一個開源專案,是一個強大的,高效能,高質量的Code生成類庫,它可以在執行期擴充套件Java類與實現Java介面,通俗說cglib可以在執行時動態生成位元組碼。
使用cglib完成動態代理,大概的原理是:cglib繼承被代理的類,重寫方法,織入通知,動態生成位元組碼並執行,因為是繼承所以final類和方法是沒有辦法動態代理的。具體實現如下
建立被代理類:
package proxy;
public class Person {
public void sayHello() {
System.out.println("my name is Person");
}
public String getName() {
return "Person";
}
}
建立代理類:
package proxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/*
* 動態代理類
* 實現了一個方法攔截器介面
*/
public class CglibProxyIntercepter implements MethodInterceptor {
/*
* sub:cglib生成的代理物件,method:被代理物件方法,objects:方法入參,methodProxy:代理方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("執行前...");
// 呼叫方法
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("執行後...");
return result;
}
// 返回目標物件的代理物件
public Object newProxy(Object target) {
// 增強類,用於生成代理類的class檔案
Enhancer enhancer = new Enhancer();
// 設定代理類的父類為被代理類
enhancer.setSuperclass(target.getClass());
// 設定代理類的回撥方法,即這裡指定了哪個類,就會執行哪個類的intercept方法
enhancer.setCallback(this);
// 設定被代理類的載入器,保證生成類的順序和關係
enhancer.setClassLoader(target.getClass().getClassLoader());
// 生成代理類並返回例項
return enhancer.create();
}
}
解析:
所有的代理類,都需要實現MethodIntercpet介面,重寫 intercept()方法,在方法中編寫自定義邏輯。
且通過methodProxy.invokeSuper()方法執行被代理類方法。
Enhancer是一個增強類,通過設定被代理類為superClass,並指定由哪個方法攔截器來成為回撥函式callBack,在呼叫creat()方法,動態生成代理類例項。
public class Test {
public static void main(String[] args) {
Person test = new Person();
CglibProxyIntercepter methodIntercpt= new CglibProxyIntercepter();
Person proxy2 = (Person) methodIntercpt.newProxy(test);
proxy2.sayHello();
proxy2.getName();
}
}
輸出結果:
執行前...
my name is Person
執行後...
執行前...
執行後...
代理類例項也需要強制轉型,才可以使用。可以轉型為被代理類型別,和被代理類的介面型別,父型別。因為代理類是被代理類的子類。
生成代理類:
下面的原始碼是貼上別人的,我們來看一下,生成一個代理類會生成三個class檔案
public class PersonService$$EnhancerByCGLIB$$eaaaed75
extends PersonService
implements Factory
{
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;//攔截器
private static final Method CGLIB$setPerson$0$Method;//被代理方法
private static final MethodProxy CGLIB$setPerson$0$Proxy;//代理方法
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
static void CGLIB$STATICHOOK1()
{
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class localClass1 = Class.forName("com.demo.proxy.cglib.PersonService$$EnhancerByCGLIB$$eaaaed75");//代理類
Class localClass2;//被代理類PersionService
Method[] tmp95_92 = ReflectUtils.findMethods(new String[] { "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (localClass2 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$finalize$1$Method = tmp95_92[0];
CGLIB$finalize$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "finalize", "CGLIB$finalize$1");
Method[] tmp115_95 = tmp95_92;
CGLIB$equals$2$Method = tmp115_95[1];
CGLIB$equals$2$Proxy = MethodProxy.create(localClass2, localClass1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
Method[] tmp135_115 = tmp115_95;
CGLIB$toString$3$Method = tmp135_115[2];
CGLIB$toString$3$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
Method[] tmp155_135 = tmp135_115;
CGLIB$hashCode$4$Method = tmp155_135[3];
CGLIB$hashCode$4$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "hashCode", "CGLIB$hashCode$4");
Method[] tmp175_155 = tmp155_135;
CGLIB$clone$5$Method = tmp175_155[4];
CGLIB$clone$5$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
tmp175_155;
Method[] tmp223_220 = ReflectUtils.findMethods(new String[] { "setPerson", "()V" }, (localClass2 = Class.forName("com.demo.proxy.cglib.PersonService")).getDeclaredMethods());
CGLIB$setPerson$0$Method = tmp223_220[0];
CGLIB$setPerson$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "setPerson", "CGLIB$setPerson$0");
tmp223_220;
return;
}
我們通過代理類的原始碼可以看到,代理類會獲得所有在父類繼承來的方法,並且會有MethodProxy與之對應,比如 Method CGLIB$setPerson$0$Method、MethodProxy CGLIB$setPerson$0$Proxy;
方法的呼叫
//代理方法(methodProxy.invokeSuper會呼叫)
final void CGLIB$setPerson$0() {
super.setPerson();
}
//被代理方法(methodProxy.invoke會呼叫,這就是為什麼在攔截器中呼叫methodProxy.invoke會死迴圈,一直在呼叫攔截器)
public final void setPerson() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if(this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if(var10000 != null) {
//呼叫攔截器
var10000.intercept(this, CGLIB$setPerson$0$Method, CGLIB$emptyArgs, CGLIB$setPerson$0$Proxy);
} else {
super.setPerson();
}
}
呼叫過程:代理物件呼叫this.setPerson方法->呼叫攔截器->methodProxy.invokeSuper->CGLIB$setPerson$0->被代理物件setPerson方法