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

目錄

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

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

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

動態代理和靜態代理其實均可以當作是對代理模式的一個使用,什麼是代理模式呢?設計模式

給某個對象提供一個代理對象,並由代理對象提供和控制對原對象的訪問。 代理模式實際上是一個很簡單的設計模式,具體的細節小夥伴們能夠本身百度,這裏就很少作介紹。緩存

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

Java動態代理的簡單使用

動態代理的相關類

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

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

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

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提的,反正不是博主,你懂得😁) 這個時候咱們能夠作以下編碼:ide

//建立一個處理器類實現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之類的其餘方法調用形成影響。this

動態代理的原理解讀

咱們知道動態代理和靜態代理的一大區別沒法就是建立代理類的過程不同,而動態代理中建立代理類的操做主要是在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相關的文章。

相關文章
相關標籤/搜索