關於Java中的動態代理,我們首先需要了解的是一種常用的設計模式——代理模式,而對於代理,根據建立代理類的時間點,又可以分為靜態代理和動態代理。
靜態代理
1、靜態代理
靜態代理:由程式設計師建立或特定工具自動生成原始碼,也就是在編譯時就已經將介面,被代理類,代理類等確定下來。在程式執行之前,代理類的。class檔案就已經生成。
2、靜態代理簡單實現
根據上面代理模式的類圖,來寫一個簡單的靜態代理的例子。我這兒舉一個比較粗糙的例子,假如一個班的同學要向老師交班費,但是都是透過班長把自己的錢轉交給老師。這裡,班長就是代理學生上交班費,
班長就是學生的代理。
首先,我們建立一個Person介面。這個介面就是學生(被代理類),和班長(代理類)的公共介面,他們都有上交班費的行為。這樣,學生上交班費就可以讓班長來代理執行。
/**
* 建立Person介面
* @author ChenHao
*/
public interface Person {
//上交班費
void giveMoney();
}
Student類實現Person介面。Student可以具體實施上交班費的動作。
public class Student implements Person {
private String name;
public Student(String name) {
this。name = name;
}
@Override
public void giveMoney() {
System。out。println(name + “上交班費50元”);
}
}
StudentsProxy類,這個類也實現了Person介面,但是還另外持有一個學生類物件,由於實現了Peson介面,同時持有一個學生物件,那麼他可以代理學生類物件執行上交班費(執行giveMoney()方法)行為。
/**
* 學生代理類,也實現了Person介面,儲存一個學生實體,這樣既可以代理學生產生行為
* @author ChenHao
*
*/
public class StudentsProxy implements Person{
//被代理的學生
Student stu;
public StudentsProxy(Student stu) {
this。stu = stu;
}
//代理上交班費,呼叫被代理學生的上交班費行為
public void giveMoney() {
stu。giveMoney();
}
}
下面測試一下,看如何使用代理模式:
public class StaticProxyTest {
public static void main(String[] args) {
//被代理的學生張三,他的班費上交有代理物件monitor(班長)完成
Student zhangsan = new Student(“張三”);
//生成代理物件,並將張三傳給代理物件
Person monitor = new StudentsProxy(zhangsan);
//班長代理上交班費
monitor。giveMoney();
}
}
執行結果:
這裡並沒有直接透過張三(被代理物件)來執行上交班費的行為,而是透過班長(代理物件)來代理執行了。這就是代理模式。
代理模式最主要的就是有一個公共介面(Person),一個具體的類(Student),一個代理類(StudentsProxy),代理類持有具體類的例項,代為執行具體類例項方法。上面說到,代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。這裡的間接性就是指不直接呼叫實際物件的方法,那麼我們在代理過程中就可以加上一些其他用途。就這個例子來說,加入班長在幫張三上交班費之前想要先反映一下張三最近學習有很大進步,透過代理模式很輕鬆就能辦到:
這裡並沒有直接透過張三(被代理物件)來執行上交班費的行為,而是透過班長(代理物件)來代理執行了。這就是代理模式。
代理模式最主要的就是有一個公共介面(Person),一個具體的類(Student),一個代理類(StudentsProxy),代理類持有具體類的例項,代為執行具體類例項方法。上面說到,代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。這裡的間接性就是指不直接呼叫實際物件的方法,那麼我們在代理過程中就可以加上一些其他用途。就這個例子來說,加入班長在幫張三上交班費之前想要先反映一下張三最近學習有很大進步,透過代理模式很輕鬆就能辦到:
/**
* 學生代理類,也實現了Person介面,儲存一個學生實體,這樣既可以代理學生產生行為
* @author ChenHao
*
*/
public class StudentsProxy implements Person{
//被代理的學生
Student stu;
public StudentsProxy(Student stu) {
this。stu = stu;
}
//代理上交班費,呼叫被代理學生的上交班費行為
public void giveMoney() {
System。out。println(“張三最近學習有進步!”);
stu。giveMoney();
}
}
模式優缺點
優點
1、 代理模式能夠協調呼叫者和被呼叫者,在一定程度上降低了系統的耦合度。
2、 代理物件可以在客戶端和目標物件之間起到中介的作用,這樣起到了的作用和保護了目標物件的
缺點
1、由於在客戶端和真實主題之間增加了代理物件,因此有些型別的代理模式可能會造成請求的處理速度變慢。
2、 實現代理模式需要額外的工作,有些代理模式的實現非常複雜。
動態代理
1。動態代理
前面介紹了靜態代理,雖然靜態代理模式很好用,但是靜態代理還是存在一些侷限性的,比如使用靜態代理模式需要程式設計師手寫很多程式碼,這個過程是比較浪費時間和精力的。一旦需要代理的類中方法比較多,或者需要同時代理多個物件的時候,這無疑會增加很大的複雜度。
代理類在程式執行時建立的代理方式被成為動態代理。 我們上面靜態代理的例子中,代理類(studentProxy)是自己定義好的,在程式執行之前就已經編譯完成。然而動態代理,代理類並不是在Java程式碼中定義的,而是在執行時根據我們在Java程式碼中的“指示”動態生成的。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類中的方法。
2、動態代理簡單實現
在java的java。lang。reflect包下提供了一個Proxy類和一個InvocationHandler介面,透過這個類和這個介面可以生成JDK動態代理類和動態代理物件。
public class DynamicProxyTest {
interface IHello {
void sayHello();
}
static class Hello implements IHello {
@Override
public void sayHello() {
System。out。println(“hello world”);
}
}
static class DynamicProxy implements InvocationHandler {
Object originalObj;
Object bind(Object originalObj) {
this。originalObj = originalObj;
return Proxy。newProxyInstance(originalObj。getClass()。getClassLoader(), originalObj。getClass()。getInterfaces(), this);
}
/**
*Object proxy是代理的物件, Method method是真實物件中呼叫方法的Method類, Object[] args是真實物件中呼叫方法的引數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System。out。println(“welcome”);
return method。invoke(originalObj, args);
}
}
public static void main(String[] args) {
IHello hello = (IHello) new DynamicProxy()。bind(new Hello());
hello。sayHello();
}
}
執行結果如下:
welcome
hello world
回到頂部
動態代理原理分析
推薦部落格
程式設計師寫程式碼之外,如何再賺一份工資?
上面說到,動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類中的方法。是因為所有被代理執行的方法,都是透過在InvocationHandler中的invoke方法呼叫的,所以我們只要在invoke方法中統一處理,就可以對所有被代理的方法進行相同的操作了。上述程式碼裡,唯一的“黑釐子”就是Proxy。newProxyInstance()方法,除此之外再沒有任何特殊之處。
在JDK動態代理中涉及如下角色:
業務介面Interface、業務實現類target、業務處理類Handler、JVM在記憶體中生成的動態代理類$Proxy0
動態代理原理圖:
說白了,動態代理的過程是這樣的:
Proxy透過傳遞給它的引數(interfaces/invocationHandler)生成代理類$Proxy0;
Proxy透過傳遞給它的引數(ClassLoader)來載入生成的代理類$Proxy0的位元組碼檔案;
動態代理的關鍵程式碼就是Proxy。newProxyInstance(classLoader, interfaces, handler),我們跟進原始碼看看
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
// handler不能為空
if (h == null) {
throw new NullPointerException();
}
final Class<?>[] intfs = interfaces。clone();
final SecurityManager sm = System。getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection。getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class。
*/
// 透過loader和介面,得到代理的Class物件
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler。
*/
try {
final Constructor<?> cons = cl。getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper。needsNewInstanceCheck(cl)) {
// create proxy instance with doPrivilege as the proxy class may
// implement non-public interfaces that requires a special permission
return AccessController。doPrivileged(new PrivilegedAction
public Object run() {
return newInstance(cons, ih);
}
});
} else {
// 建立代理物件的例項
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e。toString());
}
}
我們看一下newInstance方法的原始碼:
private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
try {
return cons。newInstance(new Object[] {h} );
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e。toString());
} catch (InvocationTargetException e) {
Throwable t = e。getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t。toString());
}
}
}
講解完了代理類的生成原始碼,我們一定想要看看代理類的程式碼是什麼樣的,下面提供一個生成代理類的方法供大家使用:
/**
* 代理類的生成工具
* @author ChenHao
* @since 2019-4-2
*/
public class ProxyGeneratorUtils {
/**
* 把代理類的位元組碼寫到硬碟上
* @param path 儲存路徑
*/
public static void writeProxyClassToHardDisk(String path) {
// 第一種方法
// System。getProperties()。put(“sun。misc。ProxyGenerator。saveGeneratedFiles”, true);
// 第二種方法
// 獲取代理類的位元組碼
byte[] classFile = ProxyGenerator。generateProxyClass(“$Proxy11”, UserServiceImpl。class。getInterfaces());
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
out。write(classFile);
out。flush();
} catch (Exception e) {
e。printStackTrace();
} finally {
try {
out。close();
} catch (IOException e) {
e。printStackTrace();
}
}
}
public static void main(String[] args) {
ProxyGeneratorUtils。writeProxyClassToHardDisk(“C:/x/$Proxy11。class”);
}
}
此時就會在指定的C盤x資料夾下生成代理類的。class檔案,我們看下反編譯後的結果:
package org。fenixsoft。bytecode;
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 DynamicProxyTest。IHello
{
private static Method m3;
private static Method m1;
private static Method m0;
private static Method m2;
/**
*注意這裡是生成代理類的構造方法,方法引數為InvocationHandler型別,看到這,是不是就有點明白
*super(paramInvocationHandler),是呼叫父類Proxy的構造方法。
*父類持有:protected InvocationHandler h;
*Proxy構造方法:
* protected Proxy(InvocationHandler h) {
* Objects。requireNonNull(h);
* this。h = h;
* }
*
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
/**
*
*這裡呼叫代理物件的sayHello方法,直接就呼叫了InvocationHandler中的invoke方法,並把m3傳了進去。
*this。h。invoke(this, m3, null); this。h就是父類Proxy中儲存的InvocationHandler例項變數
*來,再想想,代理物件持有一個InvocationHandler物件,InvocationHandler物件持有一個被代理的物件,
*再聯絡到InvacationHandler中的invoke方法。嗯,就是這樣。
*/
public final void sayHello()
throws
{
try
{
this。h。invoke(this, m3, null);
return;
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
// 此處由於版面原因,省略equals()、hashCode()、toString()三個方法的程式碼
// 這3個方法的內容與sayHello()非常相似。
static
{
try
{
m3 = Class。forName(“org。fenixsoft。bytecode。DynamicProxyTest$IHello”)。getMethod(“sayHello”, new Class[0]);
m1 = Class。forName(“java。lang。Object”)。getMethod(“equals”, new Class[] { Class。forName(“java。lang。Object”) });
m0 = Class。forName(“java。lang。Object”)。getMethod(“hashCode”, new Class[0]);
m2 = Class。forName(“java。lang。Object”)。getMethod(“toString”, new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException。getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException。getMessage());
}
}
}
這個代理類的實現程式碼也很簡單,它為傳入介面中的每一個方法,以及從 java。lang。Object中繼承來的equals()、hashCode()、toString()方法都生成了對應的實現 ,並且統一呼叫了InvocationHandler物件的invoke()方法(程式碼中的“this。h”就是父類Proxy中儲存的InvocationHandler例項變數)來實現這些方法的內容,各個方法的區別不過是傳入的引數和Method物件有所不同而已,所以無論呼叫動態代理的哪一個方法,實際上都是在執行InvocationHandler。invoke()中的代理邏輯。