Java動態代理從入門到原理再到實戰

目錄

  • 前言
  • 什麼是動態代理,和靜態代理有什麼區別
  • Java動態代理的簡單使用
  • Java動態代理的原理解讀
  • 動態代理在Android中的使用

前言

相信動態代理這個詞對於很多Android開發的小夥伴來說既熟悉又陌生,熟悉是應爲可能常常會聽一些羣裏,博客上的裝B能手掛在嘴邊,陌生是因爲在日常的Android開發中似乎沒有用到過這個東西,也沒有自己去學過這個東西(特別是培訓班出來的小夥伴們,據我說知大部分Android培訓班是不會說這東西的,少部分也只是簡單提一下就略過了)。而這種熟悉又陌生的感覺讓某些小夥伴覺得動態代理很高級,是那些大佬們才用的知識。而我,今天就幫助小夥伴們把動態代理拉下神壇,下次再有裝逼小能手和你說動態代理的時候你就敲着他的腦袋說L(老)Z(子)也會😁。

什麼是動態代理,和靜態代理有什麼區別

動態代理和靜態代理其實都可以看成是對代理模式的一個使用,什麼是代理模式呢?

給某個對象提供一個代理對象,並由代理對象提供和控制對原對象的訪問。
代理模式其實是一個很簡單的設計模式,具體的細節小夥伴們可以自己百度,這裏就不多做介紹。

靜態代理中,需要爲被代理的類(委託類)手動編寫一個代理類,爲需要被代理的每一個方法都寫一個新的方法,這部分在編譯之前就需要完成了。而動態代理則是可以在運行中動態生成一個代理類,並且不需要去手動的實現每個被代理的方法。簡單來說委託類中有1000個方法需要被代理(比如代理的目的就是大家常常用來舉例的在每個方法執行前後額外打印一段輸出),使用靜態代理你需要手動的編寫代理類並且實現這1000個方法,而使用靜態代理你則需要簡單的幾行代碼就能實現這個問題。

Java動態代理的簡單使用

動態代理的相關類

動態代理主要兩個相關類:

  • Proxy(java.lang.reflect包下的),主要負責管理和創建代理類的工作。
  • InvocationHandler 接口,只擁有一個invoke方法,主要負責方法調用部分,是動態代理中我們需要實現的方法
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
//三個參數分別是 代理類proxy  委託類被執行的方法method 方法傳入的參數args
//返回值則是method方法執行的返回值

下面我們來舉個具體的使用例子,假設有下面這麼一個接口和兩個類來負責對訂單的操作

public interface OrderHandler {
    void handler(String orderId);
}

public class NormalOrderHandler implements OrderHandler {
    @Override
    public void handler(String orderId) {
        System.out.println("NormalOrderHandler.handler():orderId:"+orderId);
    }
}

public class MaxOrderHandler implements OrderHandler {
    @Override
    public void handler(String orderId) {
        System.out.println("MaxOrderHandler.handler():orderId:"+orderId);
    }
}

而這個時候要求我們在每個handler方法調用前後都打印一串信息,並且當orderId的長度大於10時截取前10位(不要問我這種需求是哪個RZ提的,反正不是博主,你懂得😁)
這個時候我們可以做如下編碼:

//創建一個處理器類實現InvocationHandler接口並實現invoke方法
public class OrderHandlerProxy implements InvocationHandler {

    //委託類 在這裏就相當於實現了OrderHandler的類的對象
    Object target;

    public Object bind(Object target){
        this.target=target;

        //重點之一,通過Proxy的靜態方法創建代理類 第一個參數爲委託類的類加載器,
        //第二個參數爲委託類實現的接口集,第三個參數則是處理器類本身
        return Proxy.newProxyInstance(this.target.getClass().getClassLoader(),
                this.target.getClass().getInterfaces(),this);

    }
    
    //重點之二
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        //判斷執行的方法是否是我們需要代理的handler方法
        if (method.getName().equalsIgnoreCase("handler")){
            System.out.println("OrderHandlerProxy.invoke.before");
            String orderId= (String) args[0];

            //對orderId的長度做限制
            if (orderId.length()>=10){
                orderId=orderId.substring(0,10);
            }
            
            //重點之三,這個地方通過反射調用委託類的方法
            Object invoke = method.invoke(target, orderId);
            
            System.out.println("OrderHandlerProxy.invoke.after");
            return invoke;
        }else {
            //當前執行的方法不是我們需要代理的方法時不做操作直接執行委託的相應方法
            System.out.println("Method.name:"+method.getName());
            return method.invoke(target,args);
        }
    }
}

接下來則是具體的使用動態代理的代碼

public class NormalApplicationClass {
    public void handlerOrder(OrderHandler orderHandler,String orderId){
        //創建處理器對象
        OrderHandlerProxy proxy=new OrderHandlerProxy();
        //爲傳入的實現了OrderHandler接口的對象創建代理類並實例化對象
        OrderHandler handler = (OrderHandler) proxy.bind(orderHandler);
        
        handler.handler(orderId);
        System.out.println(handler.toString());
    }
    public static void main(String[] args) {
        NormalApplicationClass app=new NormalApplicationClass();
        app.handlerOrder(new MaxOrderHandler(),"012345678999");

    }
}

具體的返回值如下

OrderHandlerProxy.invoke.before
MaxOrderHandler.handler():orderId:0123456789
OrderHandlerProxy.invoke.after
Method.name:toString
com.against.javase.rtti.use.MaxOrderHandler@d716361

由上面可見,我們成功的在執行handler方法前後打印了一串東西,並且對orderId進行了長度限制,同時並不會影響對象本身的像toString之類的其他方法調用造成影響。

動態代理的原理解讀

我們知道動態代理和靜態代理的一大區別無法就是創建代理類的過程不一樣,而動態代理中創建代理類的操作主要是在Proxy.newProxyInstance()方法中,那我們主要來看下這個方法的源碼

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            throws IllegalArgumentException
    {
        //省略掉部分代碼,主要是一些Jvm安全性的驗證和權限的驗證

        /*
         * 獲取或者生成一個代理類的Class對象。爲什麼是獲取或生成呢?
         * 這是應爲有緩存機制的存在,
         * 當第二次調用newProxyInstance方法並且上次生成的代理類的緩存還未過期時會直接獲取
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * 通過反射獲取代理類的構造器,並通過構造器生成代理類對象
         */
        try {
            //...

            //通過反射獲取代理類的構造器,
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }

            //通過構造器生成代理對象,這裏返回的就是給我們使用的代理類的對象了
            return cons.newInstance(new Object[]{h});
        } //....省略掉一些無關代碼
    }

通過上面代碼不然看出Proxy是先拿到代理類的Class對象,然後通過反射獲取構造器,從構造器注入InvocationHandler實例(就是我們自己實現的處理器類)並創建代理類的實例。而獲取代理類的Class對象的操作則在Class<?> cl = getProxyClass0(loader, intfs);這裏,那麼我們繼續跟蹤,發現裏面非常簡單,只是做了一些常規的檢查並且調用了proxyClassCache.get(loader, interfaces);。我們查看發現

    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new     ProxyClassFactory());
//看到WeakCache我們就能大概猜到這東西是做什麼的了吧?而看ProxyClassFactory的類名我們不難看出創建代理類的Class對象的操作都在這裏面

而這個類裏面也非常簡單,主要方法就是其中的 apply方法。

@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
//...前面主要是一些生成proxyName,代理類類名的操作,省略掉
//具體的生成操作就是在這個方法裏面
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
try {
    //這個方法是一個native方法,通過類加載器和類名以及類的文件的字節流來加載出Class對象。
    return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        throw new IllegalArgumentException(e.toString());
     }
}

接下來已經可以定位了類的生成操作就在ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);中
這部分的代碼比較繁雜,我們只是簡單的看一個地方。

        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
//通過這個地方我們知道了,爲什麼我們對代理類的toString等方法的調用也會走代理類的invoke方法,這是應爲在生成委託類的時候幫我們重寫了這幾個方法。

//生成一個帶有InvocationHandler參數的構造器
private ProxyGenerator.MethodInfo generateConstructor() throws IOException {
        ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
        DataOutputStream var2 = new DataOutputStream(var1.code);
        this.code_aload(0, var2);
        this.code_aload(1, var2);
        var2.writeByte(183);
        var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));
        var2.writeByte(177);
        var1.maxStack = 10;
        var1.maxLocals = 2;
        var1.declaredExceptions = new short[0];
        return var1;
    }

而接下來的操作無非是根據委託類實現的接口,來生成相應的代理方法,並且生成一個需要傳遞InvocationHandler對象的構造器,只不過這裏生成的是.class文件,而不是我們手動編寫的.java文件,而.class文件是已經編譯過的文件,jvm可以直接加載進去執行,省去了編譯這一步驟。
下面我們看看Proxy爲我們生成的代理類是什麼樣子的:

public final class $Proxy0 extends Proxy implements OrderHandler {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

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

    public final void handler(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.against.javase.rtti.use.OrderHandler").getMethod("handler", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

我們可以看到這個代理類本質上是實現了委託類的接口,並且把每一個對委託類實現的接口方法的調用傳遞到我們編寫的處理器類的invoke方法上,一切就清晰明瞭,動態代理並不神祕哈哈😁。

動態代理在Android中的使用

紙上得來終覺淺,絕知此事要躬行
這句話說得很有道理,博主在學習的過程中深刻感覺到看一千遍不如自己來寫一遍,有些知識點看着感覺懂了,不自己寫根本發現不了一些問題,不自己寫也很難有深刻的映像。現在大部分博客講到動態代理的時候都會想我上面的一樣舉一個簡單的例子講一下相關方法就結束了,讓人看完感覺什麼都懂了,又好像什麼都沒看懂。

實戰之簡單模仿butterknife(黃油刀)的功能

(前排提示閱讀這部分內容需要一些反射的知識)
在選擇具體的例子時,我想了一些東西,比如hook SystemManager,但是這部分相比較於動態代理的東西更多的還是和Android系統層面相關的東西,寫起來也比較繁瑣而且也很難用很少的代碼做出一些有意思的操作。最後我想了決定模仿一下黃油刀的簡單的功能,說起黃油刀大家應該都不陌生,下面這兩個註解肯定都用過:

@BindView()
@OnClick()

今天我們就來實現下這兩個註解的功能,下面直接上代碼
先定義兩個註解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
    int value();
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    int[] value();
}

接着在Activity中使用這兩個註解

public class MainActivity extends AppCompatActivity {

    @InjectView(R.id.tv)
    private TextView tv;

    @InjectView(R.id.iv)
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewInject.inject(this);
        tv.setText("inject success!");

        iv.setImageDrawable(getResources().getDrawable(R.mipmap.ic_launcher));
    }

    @OnClick({R.id.tv,R.id.iv})
    public void onClick(View view){
        switch (view.getId()){
            case R.id.tv:
                Toast.makeText(this,"onClick,TV",Toast.LENGTH_SHORT).show();
                break;
            case R.id.iv:
                Toast.makeText(this,"onClick,IV",Toast.LENGTH_SHORT).show();
                break;
        }
    }

}

這部分代碼很簡單,沒有什麼好說的,重點在ViewInject.inject(this);這裏

public class ViewInject {

    public static void inject(Activity activity) {

        Class<? extends Activity> activityKlazz = activity.getClass();

        try {

            injectView(activity,activityKlazz);
            proxyClick(activity,activityKlazz);

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private static void proxyClick(Activity activity,Class<? extends Activity> activityKlazz) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        Method[] declaredMethods = activityKlazz.getDeclaredMethods();

        for (Method declaredMethod : declaredMethods) {

            //獲取標記了OnClick註解的方法
            if (declaredMethod.isAnnotationPresent(OnClick.class)){
                OnClick annotation = declaredMethod.getAnnotation(OnClick.class);

                int[] value = annotation.value();

                //創建處理器類並且生成代理類,同時將activity中我們標記了OnClick的方法和處理器類綁定起來
                OnClickListenerProxy proxy=new OnClickListenerProxy();
                Object listener=proxy.bind(activity);
                proxy.bindEvent(declaredMethod);


                for (int viewId : value) {
                    Method findViewByIdMethod =
                            activityKlazz.getMethod("findViewById", int.class);

                    findViewByIdMethod.setAccessible(true);
                    View view = (View) findViewByIdMethod.invoke(activity, viewId);

                    //通過反射把我們的代理類注入到相應view的onClickListener中
                    Method setOnClickListener = view.getClass().getMethod("setOnClickListener", View.OnClickListener.class);

                    setOnClickListener.setAccessible(true);
                    setOnClickListener.invoke(view,listener);
                }
            }
        }

    }


    private static void injectView(Activity activity,Class<? extends Activity> activityKlazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

        /**
         * 注入view其實很簡單,通過反射拿到activity中標記了InjectView註解的field。
         * 然後通過反射獲取到findViewById方法,並且執行這個方法拿到view的實例
         * 接着將實例賦值給activity裏的field上
         */
        for (Field field : activityKlazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(InjectView.class)) {
                InjectView annotation = field.getAnnotation(InjectView.class);

                int viewId = annotation.value();

                    Method findViewByIdMethod =
                            activityKlazz.getMethod("findViewById", int.class);

                    findViewByIdMethod.setAccessible(true);
                    View view = (View) findViewByIdMethod.invoke(activity, viewId);
                    field.setAccessible(true);
                    field.set(activity, view);

            }
        }
    }

}

上面的代碼都有註釋,下面看下OnClickListenerProxy這個處理器類:

public class OnClickListenerProxy implements InvocationHandler {

    static final String TAG="OnClickListenerProxy";

    //這個其實就是我們相關的activity
    Object delegate;

    //這個是activity中我們標記了OnClick的方法,最終的操作就是把對OnClickListener中OnClick方法的調用替換成對這個event方法的調用
    Method event;


    public Object bind(Object delegate){
        this.delegate=delegate;
        //生成代理類,這個沒什麼好說的了
        return Proxy.newProxyInstance(this.delegate.getClass().getClassLoader(),
                new Class[]{View.OnClickListener.class},this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Log.e(TAG,"invoke");
        Log.e(TAG,"method.name:"+method.getName()+"  args:"+args);

        //判斷調用的是onClick方法的話,替換成對我們event方法的調用。
        if ("onClick".equals(method.getName())){
            for (Object arg : args) {
                Log.e(TAG,"arg:"+arg);
            }
            View view= (View) args[0];
            return event.invoke(delegate,view);
        }
        return method.invoke(delegate,args);
    }
    public void bindEvent(Method declaredMethod) {
        event=declaredMethod;
    }
}

通過上面的代碼實現,我們已經能夠將項目運行並且成功實現了我們需要的功能。當然很有很多細節需要處理,不過作爲展示學習已經足夠用了,而且通過我們自己的方法也實現了黃油刀這種大名鼎鼎的開源庫的部分功能是不是很Cool呢?

不過博主並不建議再自己項目中使用這些,大家還是使用黃油刀吧,因爲反射和動態代理都會帶來一部分的性能損耗,而黃油刀使用的是編譯時註解的形式實現上面的功能的,在運行時不會帶來性能的損耗,感興趣的小夥伴們可以去Google相關的文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章