說明:上一節(jié)吉挣,我們一起學習了靜態(tài)代理际跪,本章我將繼續(xù)動態(tài)代理的講述幔戏,不過本章的內容是建立在第一章知識點的基礎上進行的找颓。二者之間有著邏輯上的必然聯(lián)系彪蓬。如果你還沒有看過設計模式--代理模式(Proxy Pattern)-靜態(tài)代理呛牲,我建議你先看一下,會對本節(jié)內容的理解有所幫助伟阔!
動態(tài)代理
(1) 動態(tài)代理
代理類在程序運行時創(chuàng)建的代理方式被成為動態(tài)代理狮鸭。 我們上面靜態(tài)代理的例子中春弥,代理類(studentProxy)是自己定義好的逃呼,在程序運行之前就已經編譯完成增炭。然而動態(tài)代理隙姿,代理類并不是在Java代碼中定義的饲嗽,而是在運行時根據(jù)我們在Java代碼中的“指示”動態(tài)生成的叶圃。相比于靜態(tài)代理鹿蜀, 動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理,而不用修改每個代理類中的方法琐簇。 比如說,想要在每個代理的方法前都加上一個處理方法:
public void giveMoney() {
//調用被代理方法前加入處理方法
beforeMethod();
stu.giveMoney();
}
說明:
這里只有一個giveMoney方法座享,就寫一次beforeMethod方法婉商,但是如果除了giveMonney還有很多其他的方法,那就需要寫很多beforeMethod方法渣叛,麻煩丈秩。那看看下面動態(tài)代理如何實現(xiàn)。
(2) 動態(tài)代理簡單實現(xiàn)
自JDK1.3開始淳衙,Java語言通過在java.lang.reflect庫中提供下面三個類直接支持代理:Proxy蘑秽、Method和InvocationHandler。
其中Proxy使得設計師能夠在運行時創(chuàng)建代理對象箫攀,其類圖如下圖所示:
那么代理對象的創(chuàng)建過程又到底是怎樣的呢肠牲?
<1> 創(chuàng)建一個動態(tài)代理對象的過程:
1.創(chuàng)建一個InvocationHandler對象
//創(chuàng)建一個與代理對象相關聯(lián)的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
2.使用Proxy類的getProxyClass靜態(tài)方法生成一個動態(tài)代理類的Class對象stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(),
new Class<?>[] {Person.class});
3.獲得stuProxyClass 中一個帶InvocationHandler參數(shù)的構造器constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
4.通過構造器constructor來創(chuàng)建一個動態(tài)實例stuProxy
Person stuProxy = (Person) cons.newInstance(stuHandler);
這樣一個動態(tài)代理對象就創(chuàng)建成功了!
然而上面的四個步驟可以通過調用Proxy的newProxyInstance()方法直接生成:
//創(chuàng)建一個與代理對象相關聯(lián)的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//創(chuàng)建一個代理對象stuProxy靴跛,代理對象的每個執(zhí)行方法都會替換執(zhí)行Invocation中的invoke方法
Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
代理對象我們是知道如何創(chuàng)建的缀雳,但是,代理對象又是如何來代替真實對象的呢梢睛?下面是一個完整的例子:
<2> 動態(tài)代理完整實例
1.定義一個Person接口
/**
* Created by yucheng on 2018/8/9.
*/
public interface Person {
// 上交班費
void giveMoney();
}
2.創(chuàng)建需要被代理的實際類Student
/**
* Created by yucheng on 2018/8/9.
*/
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班費50元");
}
}
3.創(chuàng)建StuInvocationHandler類肥印,實現(xiàn)InvocationHandler接口
這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke方法绝葡,所有執(zhí)行代理對象的方法都會被替換成執(zhí)行invoke方法深碱。再在invoke方法中執(zhí)行被代理對象target的相應方法。當然藏畅,在代理過程中敷硅,我們在真正執(zhí)行被代理對象的方法前加入自己其他處理。這也是Spring中的AOP實現(xiàn)的主要原理墓赴。
/**
* Created by yucheng on 2018/8/9.
*/
public class StuInvocationHandler implements InvocationHandler {
//invocationHandler持有的被代理對象
// 如果知道竞膳,被代理類的類型,可以在這里直接設定特定類型诫硕,需要注意的是
// 在invoke()方法中也需要轉為相應類型坦辟,而不能使用O
Object target;
public StuInvocationHandler(Object target) {
this.target = target;
}
/**
* proxy:代表動態(tài)代理對象
* method:代表正在執(zhí)行的方法
* args:代表調用目標方法時傳入的實參
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執(zhí)行" +method.getName() + "方法");
// 在方法調用之前進行系列操作,這里可以是方法
System.out.println("Before the function");
// 注意:對args進行操作之前需要進行為空的判斷,因為有的方法不帶參數(shù),不進行判斷褒脯,會拋出NullPointerException
if (args != null){
for (Object arg : args) {
System.out.println(" " + arg);
}
}
Object result = method.invoke(target, args);
System.out.println("After the function");
return result;
}
}
4.創(chuàng)建代理對象衫生,并測試
/**
* Created by yucheng on 2018/8/9.
*/
public class ProxyTest {
public static void main(String[] args) {
//創(chuàng)建一個實例對象列敲,這個對象是被代理的對象
Person zhangsan = new Student("張三");
//創(chuàng)建一個與代理對象相關聯(lián)的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler(zhangsan);
//創(chuàng)建一個代理對象stuProxy來代理zhangsan彼棍,代理對象的每個執(zhí)行方法都會替換執(zhí)行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(
Person.class.getClassLoader(),
// Or zhangsan.getClass().getInterfaces(),
new Class<?>[]{Person.class},
stuHandler);
//代理執(zhí)行上交班費的方法
stuProxy.giveMoney();
}
}
5.輸出結果
我們執(zhí)行這個ProxyTest類并巍,先想一下咐扭,我們創(chuàng)建了一個需要被代理的學生張三子寓,將zhangsan對象傳給了stuHandler中梁厉,我們在創(chuàng)建代理對stuProxy時辜羊,將stuHandler作為參數(shù)了的,上面也有說到所有執(zhí)行代理對象的方法都會被替換成執(zhí)行invoke方法词顾,也就是說八秃,最后執(zhí)行的是StuInvocationHandler中的invoke方法。因此出現(xiàn)下面的結果也就正常了肉盹。
代理執(zhí)行giveMoney方法
Before the function
張三上交班費50元
After the function
動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理昔驱,而不用修改每個代理類中的方法。是因為所有被代理執(zhí)行的方法上忍,都是通過在InvocationHandler中的invoke方法調用的骤肛,所以我們只要在invoke方法中統(tǒng)一處理,就可以對所有被代理的方法進行相同的操作了窍蓝。例如腋颠,這里的方法計時,所有的被代理對象執(zhí)行的方法都會被計時它抱,然而我只做了很少的代碼量秕豫。
動態(tài)代理的過程,代理對象和被代理對象的關系不像靜態(tài)代理那樣一目了然观蓄,清晰明了混移。因為動態(tài)代理的過程中,我們并沒有實際看到代理類侮穿,也沒有很清晰地的看到代理類的具體樣子歌径,而且動態(tài)代理中被代理對象和代理對象是通過InvocationHandler來完成的代理過程的,其中具體是怎樣操作的亲茅,為什么代理對象執(zhí)行的方法都會通過InvocationHandler中的invoke方法來執(zhí)行回铛。帶著這些問題,我們就需要對java動態(tài)代理的源碼進行簡要的分析克锣,弄清楚其中緣由茵肃。
(3) 動態(tài)代理原理分析
<1> Java動態(tài)代理創(chuàng)建出來的動態(tài)代理類Proxy
我們創(chuàng)建動態(tài)代理對象,是通過Proxy的newProxyInstance()方法實現(xiàn)的袭祟,下面我們來看看它的源碼
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
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.
* 2.查找或生成指定的代理類的Class對象
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 3.通過cl和構造器參數(shù)验残,返回一個帶InvocationHandler參數(shù)的構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 1.通過參數(shù)h傳遞一個InvocationHandler對象
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 4.創(chuàng)建代理對象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
由源碼我們可以知曉,Proxy.newProxyInstance()方法是對創(chuàng)建代理對象的四個步驟的封裝巾乳。
jdk為我們的生成了一個叫$Proxy0(這個名字后面的0是編號您没,有多個代理類會一次遞增)的代理類鸟召,這個類文件時放在內存中的,我們在創(chuàng)建代理對象時氨鹏,就是通過反射獲得這個類的構造方法欧募,然后創(chuàng)建的代理實例。通過對這個生成的代理類源碼的查看仆抵,我們很容易能看出跟继,動態(tài)代理實現(xiàn)的具體過程。
我們可以對InvocationHandler看做一個中介類镣丑,中介類持有一個被代理對象还栓,在invoke方法中調用了被代理對象的相應方法。通過聚合方式持有被代理對象的引用传轰,把外部對invoke的調用最終都轉為對被代理對象的調用。
代理類調用自己方法時谷婆,通過自身持有的中介類對象來調用中介類對象的invoke方法慨蛙,從而達到代理執(zhí)行被代理對象的方法。也就是說纪挎,動態(tài)代理通過中介類實現(xiàn)了具體的代理功能
總結
生成的代理類:$Proxy0 extends Proxy implements Person期贫,我們看到代理類繼承了Proxy類,所以也就決定了java動態(tài)代理只能對接口進行代理异袄,Java的繼承機制注定了這些動態(tài)代理類們無法實現(xiàn)對class的動態(tài)代理通砍。
上面的動態(tài)代理的例子,其實就是AOP的一個簡單實現(xiàn)了烤蜕,在目標對象的方法執(zhí)行之前和執(zhí)行之后進行了處理封孙,對方法耗時統(tǒng)計。Spring的AOP實現(xiàn)其實也是用了Proxy和InvocationHandler這兩個東西的讽营。
說明:
本文是本人綜合<<Java與模式>>以及一些好的博客的內容虎忌,再加上個人的一些心得,總結而成的橱鹏。僅僅出于學習的目的膜蠢。
參考鏈接:
1.https://www.cnblogs.com/gonjan-blog/p/6685611.html
2.http://www.runoob.com/design-pattern/proxy-pattern.html
推薦閱讀:
代理模式之“高老莊悟空降八戒”