定義
為其它對象提供一種代理以控制這個對這個對象的通路。
不管是靜态代理還是動态代理,目的都是要拿到目标對象的引用,并且能夠調用到目标類的業務方法。
靜态代理
- 人的抽象接口
package com.faith.net.proxy.staticed;
/**
* 人抽象接口
*/
public interface Person {
public void drive();
}
- Boss作為被代理對象
package com.faith.net.proxy.staticed;
/**
* 老闆, 雇傭者
*/
public class Boss implements Person {
@Override
public void drive() {
System.out.println("drive..");
}
}
- Employee作為代理對象
package com.faith.net.proxy.staticed;
/**
* 雇員,可以被任何人雇傭。
* 這個類的作用就是保持被代理類對象的引用,并保證能* 夠調用其方法即可。不需要實作Person類
*/
public class Employee {
private Person person;
public Employee(Person person){
this.person = person;
}
public void drive(){
System.out.println("被雇傭,開始工作:");
this.person.drive();
System.out.println("工作結束。");
}
}
- 測試類
package com.faith.net.proxy.staticed;
public class StaticProxyTest {
public static void main(String[] args) {
Employee employee = new Employee(new Boss());
employee.drive();
}
}
靜态代理的缺點,當Person添加新的方法,例如work,被代理類Boss需要實作work方法,并且代理類需要提前知道被代理的引用及其需要被代理的方法。
動态代理可以避免這些麻煩。
動态代理
動态代理中,代理類是運作期自動生成的,無需提前了解被代理類的詳細情況。
靜态代理在代理之前,所有東西都是已知的;動态代理在代理之前,所有東西都是未知的。
動态代理最終都會生成一個新的代理類。
jdk動态代理
jdk動态代理中,代理類必須實作InvocationHandler接口,詳細請見代碼注釋。而代理類通過位元組碼重組方式實作。
Person及Boss類沿用上例即可。
- 代理類
package com.faith.net.proxy.jdk;
import com.faith.net.proxy.staticed.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* jdk代理類, 本質上是作為調用處理器的實作,是以必須要實作調用處理器接口
*/
public class JDKEmployee implements InvocationHandler {
// 被代理對象的引用
private Person target;
public JDKEmployee(Person target) {
this.target = target;
}
// 擷取動态代理對象
public Object getInstance() throws Exception{
/**
* 三個參數:
*
* 1、類加載器将加載進入記憶體中
*
* 2、建立出的動态代理對象需要實作哪幾個接口
*
* 3、調用處理器,這裡可直接指定為this,替換掉new JDKEmployee(target)則為return Proxy.newProxyInstance(JDKEmployee.class.getClassLoader(), new Class[] { Person.class }, new JDKEmployee(target));
*/
return Proxy.newProxyInstance(JDKEmployee.class.getClassLoader(), new Class[] { Person.class }, new JDKEmployee(target));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* InvocationHandler的invoke()方法的參數有三個:
*
* Object proxy:代理對象,也就是Proxy.newProxyInstance()方法傳回的對象,通常不用;
*
* Method method:表示目前被調用方法的反射對象;
*
* Object[] args:表示目前被調用方法的參數,沒有參數的args是一個零長數組。
*
* invoke()方法的傳回值為Object類型,它表示目前被調用的方法的傳回值
*/
System.out.println("被雇傭,開始工作:");
Object invoke = method.invoke(this.target, args);
System.out.println("工作結束。");
return invoke;
}
}
package com.faith.net.proxy.jdk;
import com.faith.net.proxy.staticed.Person;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
/**
* 測試類
*/
public class JDKProxyTest {
public static void main(String[] args) {
try {
Person obj = (Person)new JDKEmployee(new Boss()).getInstance();
System.out.println(obj.getClass());
obj.drive();
//列印出$Proxy0類檔案,稍後通過反編譯工具可以檢視源代碼
byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class});
FileOutputStream os = new FileOutputStream("D://$Proxy0.class");
os.write(bytes);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 位元組碼重組過程
1、拿到被代理對象的引用,并且擷取到它的所有的接口;
2、JDK Proxy類重新生成一個新的類、同時新的類要實作被代理類所實作的所有接口;
3、動态生成新類的Java代碼,把新加的業務邏輯方法由一定的邏輯代碼去調用;
4、編譯新生成的Java代碼,生成.class位元組碼檔案
5、将位元組碼檔案加載到JVM中運作.
這個過程就叫位元組碼重組。
- 分析代理類
上面的
System.out.println(obj.getClass());
會輸出如下結果:
class com.sun.proxy.$Proxy0
按照JDK規範,$開頭的類都是運作時動态生成的,例如内部類。
将$Proxy0類檔案輸出并拖入idea中,可以得到反編譯結果如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.faith.net.proxy.staticed.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void drive() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.faith.net.proxy.staticed.Person").getMethod("drive");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到該類繼承了Proxy類并實作了Person接口:
public final class $Proxy0 extends Proxy implements Person
主要看代理類中的代理方法drive:
public final void drive() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
其中h是父類的,進入其父類Proxy可得:
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
在此場景h就是實作了InvocationHandler接口的代理類JDKEmployee。是以$Proxy0類中drive方法調用的即是JDKEmployee的drive方法。
這就是jdk動态代理的原理。
- 内部類替代代理類
JDKEmployee類還可以使用内部類來替代,例如:
/**
* 測試類
*/
public class JDKProxyTest {
public static void main(String[] args) {
Person obj = (Person) Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class[]{Person.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//調用方法
if ("drive".equals(method.getName())) {
System.out.println("被雇傭,開始工作:");
Object invoke = method.invoke(new Boss(), args);
System.out.println("工作結束。");
return invoke;
} else {
return null;
}
}
});
}
}
因為JDKEmployee存在的意義就是作為調用處理器的實作,那麼這個實作當然可以使用内部類來替代。
CGLIB動态代理
jdk動态代理是基于接口實作的,而CGLIB動态代理是通過繼承實作的。
同樣,CGLIB方式需要代理類實作MethodInterceptor接口,其意義也是作為方法的處理器。示例如下:
- Boss類
package com.faith.net.proxy.cglib;
/**
* Boss
*/
public class Boss {
public void drive(){
System.out.println("drive..");
}
}
package com.faith.net.proxy.cglib;
/**
* 測試
*/
public class CglibTest {
public static void main(String[] args) {
try {
Boss obj = (Boss)new CglibEmployee().getInstance(Boss.class);
obj.drive();
System.out.println(obj.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}