提及java動態代理,在我剛開始學java時對這項技術也是十分困惑,明明能夠直接調通的對象方法爲何還要使用動態代理?隨着學習的不斷深刻和工做經驗的積累,慢慢的體會並理解了java動態代理機制。昨天再給公司新同事作技術培訓時有同窗就對動態代理產生了疑問,我這裏梳理一遍一併記錄一下,方便你們查看對本身也是加深記憶。java
(1)什麼是代理?編程
大道理上講代理是一種軟件設計模式,目的地但願能作到代碼重用。具體上講,代理這種設計模式是經過不直接訪問被代理對象的方式,而訪問被代理對象的方法。這個就比如 商戶---->明星經紀人(代理)---->明星這種模式。咱們能夠不經過直接與明星對話的狀況下,而經過明星經紀人(代理)與其產生間接對話。設計模式
(2)什麼狀況下使用代理?api
(1)設計模式中有一個設計原則是開閉原則,是說對修改關閉對擴展開放,咱們在工做中有時會接手不少前人的代碼,裏面代碼邏輯讓人摸不着頭腦(sometimes the code is really like shit),這時就很難去下手修改代碼,那麼這時咱們就能夠經過代理對類進行加強。框架
(2)咱們在使用RPC框架的時候,框架自己並不能提早知道各個業務方要調用哪些接口的哪些方法 。那麼這個時候,就可用經過動態代理的方式來創建一箇中間人給客戶端使用,也方便框架進行搭建邏輯,某種程度上也是客戶端代碼和框架鬆耦合的一種表現。ide
(3)Spring的AOP機制就是採用動態代理的機制來實現切面編程。學習
(3)靜態代理和動態代理測試
咱們根據加載被代理類的時機不一樣,將代理分爲靜態代理和動態代理。若是咱們在代碼編譯時就肯定了被代理的類是哪個,那麼就能夠直接使用靜態代理;若是不能肯定,那麼可使用類的動態加載機制,在代碼運行期間加載被代理的類這就是動態代理,好比RPC框架和Spring AOP機制。ui
(4)靜態代理this
咱們先建立一個接口,遺憾的是java api代理機制求被代理類必需要實現某個接口,對於靜態代理方式代理類也要實現和被代理類相同的接口;對於動態代理代理類則不須要顯示的實現被代理類所實現的接口。
/**
* 頂層接口
* @author yujie.wang
*
*/
public interface Person {
public void sayHello(String content, int age);
public void sayGoodBye(boolean seeAgin, double time);
}
/**
* 須要被代理的類 實現了一個接口Person
* @author yujie.wang
*
*/
public class Student implements Person{
@Override
public void sayHello(String content, int age) {
// TODO Auto-generated method stub
System.out.println("student say hello" + content + " "+ age);
}
@Override
public void sayGoodBye(boolean seeAgin, double time) {
// TODO Auto-generated method stub
System.out.println("student sayGoodBye " + time + " "+ seeAgin);
}
}
/**
* 靜態代理,這個代理類也必需要實現和被代理類相同的Person接口
* @author yujie.wang
*
*/
public class ProxyTest implements Person{
private Person o;
public ProxyTest(Person o){
this.o = o;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//s爲被代理的對象,某些狀況下 咱們不但願修改已有的代碼,咱們採用代理來間接訪問
Student s = new Student();
//建立代理類對象
ProxyTest proxy = new ProxyTest(s);
//調用代理類對象的方法
proxy.sayHello("welcome to java", 20);
System.out.println("******");
//調用代理類對象的方法
proxy.sayGoodBye(true, 100);
}
@Override
public void sayHello(String content, int age) {
// TODO Auto-generated method stub
System.out.println("ProxyTest sayHello begin");
//在代理類的方法中 間接訪問被代理對象的方法
o.sayHello(content, age);
System.out.println("ProxyTest sayHello end");
}
@Override
public void sayGoodBye(boolean seeAgin, double time) {
// TODO Auto-generated method stub
System.out.println("ProxyTest sayHello begin");
//在代理類的方法中 間接訪問被代理對象的方法
o.sayGoodBye(seeAgin, time);
System.out.println("ProxyTest sayHello end");
}
}
測試代碼輸出:
ProxyTest sayHello begin
student say hellowelcome to java 20
ProxyTest sayHello end
******
ProxyTest sayHello begin
student sayGoodBye 100.0 true
ProxyTest sayHello end
靜態代理看起來是比較簡單的,沒有什麼問題只不過是在代理類中引入了被代理類的對象而已。
那麼接下來咱們看看動態代理。
(5)動態代理
咱們先直接上動態代理的代碼,以後再分析代碼的行爲,上面的Person接口和Student被代理類保持不變。
/**
* 動態代理,動態代理類不要顯示的實現被代理類所實現的接口
* @author yujie.wang
*
*/
public class MyInvocationHandler implements InvocationHandler{
private Object object;
public MyInvocationHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyInvocationHandler invoke begin");
System.out.println("proxy: "+ proxy.getClass().getName());
System.out.println("method: "+ method.getName());
for(Object o : args){
System.out.println("arg: "+ o);
}
//經過反射調用 被代理類的方法
method.invoke(object, args);
System.out.println("MyInvocationHandler invoke end");
return null;
}
public static void main(String [] args){
//建立須要被代理的類
Student s = new Student();
//這一句是生成代理類的class文件,前提是你須要在工程根目錄下建立com/sun/proxy目錄,否則會報找不到路徑的io異常
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//得到加載被代理類的 類加載器
ClassLoader loader = Thread.currentThread().getContextClassLoader();
//指明被代理類實現的接口
Class<?>[] interfaces = s.getClass().getInterfaces();
// 建立被代理類的委託類,以後想要調用被代理類的方法時,都會委託給這個類的
invoke(Object proxy, Method method, Object[] args)方法
MyInvocationHandler h = new MyInvocationHandler(s);
//生成代理類
Person proxy = (Person)Proxy.newProxyInstance(loader, interfaces, h);
//經過代理類調用 被代理類的方法
proxy.sayHello("yujie.wang", 20);
proxy.sayGoodBye(true, 100);
System.out.println("end");
}
}
運行測試代碼輸出以下結果:
MyInvocationHandler invoke begin
proxy: com.sun.proxy.$Proxy0
method: sayHello
arg: yujie.wang
arg: 20
student say helloyujie.wang 20
MyInvocationHandler invoke end
MyInvocationHandler invoke begin
proxy: com.sun.proxy.$Proxy0
method: sayGoodBye
arg: true
arg: 100.0
student sayGoodBye 100.0 true
MyInvocationHandler invoke end
end
仔細分析上面的動態代理實現代碼,咱們看到這裏涉及到java反射包下的一個接口InvocationHandler和一個類Proxy。
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
這個接口只有一個invoke方法,咱們在經過代理類調用被代理類的方法時,最終都會委託給這個invoke方法執行,
//經過代理類調用 被代理類的方法
proxy.sayHello("yujie.wang", 20);
proxy.sayGoodBye(true, 100);
因此咱們就能夠在這個invoke方法中對被代理類進行加強或作一些其餘操做。
Proxy類的public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法內部經過拼接字節碼的方式來建立代理類,後面我會反編譯出它所建立的代理類看看內容。
咱們看這個方法的三個參數:
ClassLoader loader:指定一個動態加載代理類的類加載器
Class<?>[] interfaces:指明被代理類實現的接口,以後咱們經過拼接字節碼生成的類才能知道調用哪些方法。
InvocationHandler h:這是一個方法委託類,咱們經過代理調用被代理類的方法時,就能夠將方法名和方法參數都委託給這個委託類。
你看咱們如今有了類加載器、類實現的接口、要調用方法的方法名和參數,那麼咱們就能夠作不少事情了。
(6)反編譯Proxy.newProxyInstance所建立的代理類
//這一句是生成代理類的class文件,前提是你須要在工程根目錄下建立com/sun/proxy目錄,否則會報找不到路徑的io異常
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
咱們在代碼中加入上述代碼,代碼就會保存生成的代理類,名稱爲$Proxy0.class
經過jd-gui反編譯代碼以下,其中註釋是我加上去的:
package com.sun.proxy;
import com.yujie.proxy.dynamic.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/**
*代理類也實現了Person接口,看起來和靜態代理的方式也會同樣的
*同時代理類也繼承了Proxy類
*/
public final class $Proxy0 extends Proxy implements Person{
private static Method m4;
private static Method m1;
private static Method m0;
private static Method m3;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//實現了Person接口的方法,這就是咱們調用這個方法Proxy.newProxyInstance必須提供第二個參數的做用
public final void sayGoodBye(boolean paramBoolean, double paramDouble)
throws
{
try
{
// 咱們看到經過調用代理類的方法時,最終方法都會委託給InvocationHandler實現類的invoke方法
// m4爲代理類經過反射得到的Method
this.h.invoke(this, m4, new Object[] { Boolean.valueOf(paramBoolean), Double.valueOf(paramDouble) });
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final boolean equals(Object paramObject)
throws
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//實現了Person接口的方法,這就是咱們調用這個方法Proxy.newProxyInstance必須提供第二個參數的做用
public final void sayHello(String paramString, int paramInt)
throws
{
try
{
// 咱們看到經過調用代理類的方法時,最終方法都會委託給InvocationHandler實現類的invoke方法
// m4爲代理類經過反射得到的Method
this.h.invoke(this, m3, new Object[] { paramString, Integer.valueOf(paramInt) });
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{//代理類經過反射 得到的接口方法Method
m4 = Class.forName("com.yujie.proxy.dynamic.Person").getMethod("sayGoodBye", new Class[] { Boolean.TYPE, Double.TYPE });
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]);
//代理類經過反射 得到的接口方法Method
m3 = Class.forName("com.yujie.proxy.dynamic.Person").getMethod("sayHello", new Class[] { Class.forName("java.lang.String"), Integer.TYPE });
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());
}
}
}
總結一下:
jdk的代理讓咱們在不直接訪問某些對象的狀況下,經過代理機制也能夠訪問被代理對象的方法,這種技術能夠應用在不少地方好比RPC框架,Spring AOP機制,可是咱們看到jdk的代理機制必需要求被代理類實現某個方法,這樣在生成代理類的時候才能知道從新那些方法。這樣一個沒有實現任何接口的類就沒法經過jdk的代理機制進行代理,固然解決方法是使用cglib的代理機制進行代理。