您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關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
動態代理原理圖:
說白了,動態代理的過程是這樣的:
1.Proxy通過傳遞給它的參數(interfaces/invocationHandler)生成代理類$Proxy0;
2.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<Object>() { 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中動態和靜態代理的原理是什么有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。