教你清楚瞭解JAVA動態代理

  • 代理在生活中很常見,好比說婚介網站,其實就是找對象的代理;還有社保代理、人事代理;還有找黃牛搶票,其實也是一種代理;而這些代理,在JAVA中也是有對應實現的。

一、爲何要動態代理html

動態代理的做用其實就是在不修改原代碼的前提下,對已有的方法進行加強。java

關鍵點:程序員

  • 不修改原來已有的代碼(知足設計模式的要求)
  • 對已有方法進行加強

二、舉個栗子設計模式

咱們用一個很簡單的例子來講明: Hello 類,有一個 introduction 方法。ide

如今咱們的需求就是不修改 Hello 類的 introduction 方法,在 introduction 以前先 sayHello ,在 introduction 以後再 sayGoodBye函數

三、實現方式學習

JAVA中,實現動態代理有兩種方式,一種是JDK提供的,一種是第三方庫 CgLib 提供的。特色以下:測試

 
CgLib

3.一、JDK動態代理網站

JDK動態代理須要實現接口,而後經過對接口方法的加強來實現動態代理ui

因此要使用JDK動態代理的話,咱們首先要建立一個接口,而且被代理的方法要在這個接口裏面

3.1.一、建立一個接口

咱們建立一個接口以下:

Personal.java

 
public interface Personal { /** * 被代理的方法 */ void introduction(); }

3.1.二、實現接口

建立接口實現類,而且完成 introduction 方法

PersonalImpl.java

 
public class PersonalImpl implements Personal { @Override public void introduction() { System.out.println("我是程序員!"); } }

3.1.三、建立代理類

JDK代理的關鍵就是這個代理類了,須要實現 InvocationHandler

在代理類中,全部方法的調用都好分發到 invoke 方法中。咱們在 invoke 方法完成對方法的加強便可

JDKProxyFactory.java

 
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JDKProxyFactory<T> implements InvocationHandler { /** * 目標對象 */ private T target; /** * 構造函數傳入目標對象 * * @param target 目標對象 */ public JDKProxyFactory(T target) { this.target = target; } /** * 獲取代理對象 * * @return 獲取代理 */ public T getProxy() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 對方法加強 System.out.println("你們好!"); // 調用原方法 Object result = method.invoke(target, args); // 方法加強 System.out.println("再見!"); return result; } }

就這樣,JDK動態代理的代碼就完成了,接下來寫一份測試代碼

3.1.四、編寫測試代碼

爲了方便測試,咱們編寫一個 test 方法

同時爲了查看class文件,還添加了一個 generatorClass 方法,這個方法能夠將動態代理生成的 .class 輸出到文件

ProxyTest.java

 
import org.junit.Test; import sun.misc.ProxyGenerator; import java.io.FileOutputStream; import java.io.IOException; public class ProxyTest { @Test public void testJdkProxy() { // 生成目標對象 Personal personal = new PersonalImpl(); // 獲取代理對象 JDKProxyFactory<Personal> proxyFactory = new JDKProxyFactory<>(personal); Personal proxy = proxyFactory.getProxy(); // 將proxy的class字節碼輸出到文件 generatorClass(proxy); // 調用代理對象 proxy.introduction(); } /** * 將對象的class字節碼輸出到文件 * * @param proxy 代理類 */ private void generatorClass(Object proxy) { FileOutputStream out = null; try { byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), new Class[]{proxy.getClass()}); out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class"); out.write(generateProxyClass); } catch (Exception e) { e.printStackTrace(); } finally { if (out != null) { try { out.close(); } catch (IOException e) { // TODO Auto-generated catch block } } } } }

3.1.五、查看運行結果

能夠看到,運行 test 方法以後,控制檯打印出以下:

 
你們好! 我是程序員! 再見!

咱們在 introduction 方法前和後都成功增長了功能,讓這個程序員的自我介紹瞬間變得更加有禮貌了。

3.1.六、探探動態代理的祕密

動態代理的代碼並很少,那麼JDK底層是怎麼幫咱們實現的呢?

在測試的時候咱們將動態生成的代理類的 class 字節碼輸出到了文件,咱們能夠反編譯看看。

結果有點長,就不所有貼出來了,不過咱們能夠看到,裏面有一個 introduction 方法以下:

 
/** * the invocation handler for this proxy instance. * @serial */ protected InvocationHandler h; protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } public final void introduction() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }

原來,生成的代理對象裏面,引用了咱們的 InvocationHandler ,而後在將 introduction 方法裏面調用了 InvocationHandler 的 introduction ,而 InvocationHandler 是由咱們編寫的代理類,在這裏咱們增長了 sayHello 和 sayGoodBye 操做,而後還調用了原對象的 introduction 方法,就這樣完成了動態代理。

3.二、CgLib動態代理

CgLib 動態

3.2.一、建立被代理對象

因爲 CgLib 不須要實現接口,因此咱們不須要建立接口文件了(固然,你要有接口也沒有問題)

直接建立目標類,實現 introduction 方法

PersonalImpl.java

 
public class PersonalImpl { public void introduction() { System.out.println("我是程序員!"); } }

3.2.二、建立代理類

一樣,咱們也須要建立代理類,而且在這裏實現加強的邏輯,此次咱們不是實現 InvocationHandler 接口了,而是實現 CgLib 提供的接口 MethodInterceptor ,都是相似的, MethodInterceptor 中,所有方法調用都會交給 intercept 處理,咱們在 intercept 添加處理邏輯便可。

CgLibProxyFactory.java

 
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CgLibProxyFactory<T> implements MethodInterceptor { /** * 獲取代理對象 * * @param tClass 被代理的目標對象 * @return 代理對象 */ public T getProxyByCgLib(Class<T> tClass) { // 建立加強器 Enhancer enhancer = new Enhancer(); // 設置須要加強的類的類對象 enhancer.setSuperclass(tClass); // 設置回調函數 enhancer.setCallback(this); // 獲取加強以後的代理對象 return (T) enhancer.create(); } /** * 代理類方法調用回調 * * @param obj 這是代理對象,也就是[目標對象]的子類 * @param method [目標對象]的方法 * @param args 參數 * @param proxy 代理對象的方法 * @return 返回結果,返回給調用者 * @throws Throwable */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("你們好!"); Object result = proxy.invokeSuper(obj, args); System.out.println("再見!"); return result; } }

3.2.三、編寫測試代碼

在剛纔的測試方法中,咱們添加一個 cglib 的測試方法:

 
@Test public void testCgLibProxy() { // 生成被代理的目標對象 PersonalImpl personal = new PersonalImpl(); // 獲取代理類 CgLibProxyFactory<PersonalImpl> proxyFactory = new CgLibProxyFactory<>(); PersonalImpl proxy = proxyFactory.getProxyByCgLib((Class<PersonalImpl>) personal.getClass()); // 將proxy的class字節碼輸出到文件 generatorClass(proxy); // 調用代理對象 proxy.introduction(); }

3.2.四、查看運行結果

運行測試用例,能夠看到跟JDK的實現同樣的效果

 
你們好! 我是程序員! 再見!

3.2.五、探探動態代理的祕密

跟JDK的測試同樣,咱們也來看看生成的 class 文件

 
public final void introduction() throws { try { super.h.invoke(this, m7, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }

能夠發現,與JDK的動態代理並無區別。

四、如何選擇

既然有兩種實現方式,那麼到底應該怎麼選擇呢?

就兩個原則:

  • 目標類有接口實現的, JDK 和 CgLib 均可以選擇,你開心就好
  • 目標類沒有實現任何接口,那隻能用 CgLib 了

分享一些知識點給你們但願能幫助到你們,或者從中啓發。

加入Q君羊:821169538 都是java愛好澤,你們能夠一塊兒討論交流學習

原文

http://fengqiangboy.com/15377761043880.html

相關文章
相關標籤/搜索