將cglib動態代理思想帶入Android開發

weixin_34391445發表於2017-05-08

動態代理在Android實際開發中用的並不是很多,但在設計框架的時候用的就比較多了,最近在看J2EE一些東西,像Spring,Hibernate等都有通過動態代理來實現方法增強、方法攔截等需要,通過代理的方式優雅的實現AOP程式設計。我們今天來看看這個代理究竟是什麼樣子,在Android開發中如何使用它,以及將cglib動態代理思想在Android中看看如何實現。

專案地址:MethodInterceptProxy

一、什麼是代理

通常我們說的代理,在生活中就像中介、經紀人的角色。

目標物件/被代理物件 ------ 房主:真正的租房的方法
代理物件 ------- 黑中介:有租房子的方法(呼叫房主的租房的方法)
執行代理物件方法的物件 ---- 租房的人

流程:我們要租房----->中介(租房的方法)------>房主(租房的方法)
抽象:呼叫物件----->代理物件------>目標物件

二、靜態代理

先看看比較常見的靜態代理,也就是裝飾設計模式:
首先建一個Star介面:

public interface Star {

    void singSong();
}

然後建Star子類SuperStar

public class SuperStar implements Star {

    @Override
    public void singSong() {
        System.out.println("唱歌啦--------");
    }

}

最後我們建立SuperStar的代理類SuperStarProxy

public class SuperStarProxy implements Star {

    private Star star;
    
    SuperStarProxy(Star star){
        this.star = star;
    }
    
    @Override
    public void singSong() {
        System.out.println("before-------------");
        star.singSong();
        System.out.println("after-------------");
    }
}

    public static void main(String[] args) {
        Star star = new SuperStarProxy(new SuperStar());
        star.singSong();
    }

我們將需要代理的物件傳進來生成代理物件,之後只需要使用代理物件來處理相關業務就可以了。

三、動態代理

靜態代理需要為每一個需要代理的類寫一個代理類,為每一個需要代理的方法重寫代理方法,如果有上百個類或者類裡方法很多,那重複的工作量也是很可觀的。JDK提供了動態代理方式,可以簡單理解為在JVM可以在執行時幫我們動態生成一系列的代理類,這樣我們就不需要手寫每一個靜態的代理類了。

        final Star star = new SuperStar();
        Star st = (Star) Proxy.newProxyInstance(Star.class.getClassLoader(), star.getClass().getInterfaces(), new InvocationHandler() {
            
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                System.out.println("before---------");
                Object object = method.invoke(star, args);
                System.out.println("after---------");

                return object;
            }
        });
        st.singSong();

newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

引數一:類載入器,動態代理類,執行時建立。一般情況:當前類.class.getClassLoader()
引數二:interfaces:代表與目標物件實現的所有的介面位元組碼物件陣列
引數三:具體的代理的操作,InvocationHandler介面

通過JDK提供的動態代理方式,我們可以很輕鬆的生成代理物件,但通過這種方式實現代理有個很大的限制就是:JDK的Proxy方式實現的動態代理,目標物件必須有介面,沒有介面不能實現jdk版動態代理。

四、cglib

cglib是一個功能強大,高效能的程式碼生成包。它為沒有實現介面的類提供代理,為JDK的動態代理提供了很好的補充。通常可以使用Java的動態代理建立代理,但當要代理的類沒有實現介面或者為了更好的效能,cglib是一個好的選擇。
但是但是但是,一個很致命的缺點是:cglib底層採用ASM位元組碼生成框架,使用位元組碼技術生成代理類,也就是生成的.class檔案,而我們在android中載入的是優化後的.dex檔案,也就是說我們需要可以動態生成.dex檔案代理類,cglib在android中是不能使用的。但後面我們會根據dexmaker框架來仿照動態生成.dex檔案,實現cglib的動態代理功能。
好了,我們先來看下cglib的強大吧~
舉個例子,boss安排要實現一個人員管理的增刪改查功能,那這個簡單,三兩下就搞定:

public class PeopleService {

    public void add(){
        System.out.println("add-----------");
    }
    public void delete(){
        System.out.println("delete-----------");
    }
    public void update(){
        System.out.println("update-----------");
    }
    public void select(){
        System.out.println("select-----------");
    }
}

OK,搞定~但是呢,需求是不斷變化的,過了幾天,boss又發話了,說不是每個人都可以使用這個增刪改查功能的,要指定人員才可以使用,那。。我們就改吧~最直接的方式,在每個方法上都加上判斷條件,但這麼做多少有點太挫了,如果有五十個方法,那我們這麼多方法就要都加一遍,用cglib我們可以這麼做:

        final String name = "張si";
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PeopleService.class);
        //目標物件攔截器,實現MethodInterceptor 
        //Object object為目標物件 
        //Method method為目標方法 
        //Object[] args 為引數, 
        //MethodProxy proxy CGlib方法代理物件 
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object object, Method method, Object[] args,
                    MethodProxy proxy) throws Throwable {
                Object obj = null;
                if(name.equals("張三")){
                     obj = proxy.invokeSuper(object, args); ;
                }else{
                    System.out.println("----對不起,您沒有許可權----");
                }
                return obj;
            }
        });
        PeopleService ps = (PeopleService) enhancer.create();
        ps.add();

只需新增一個攔截器,就可以攔截所有增刪改查操作,從切面進行統一管理,程式碼量也不多。
又過了幾天,boss又發話了,我們的增刪改查不是都要有限制,只有查詢才對特定人員有限制,那我們就繼續改嘍~
這個時候我們就可以加入過濾器CallbackFilter:

final String name = "張武";
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PeopleService.class);
        //目標物件攔截器,實現MethodInterceptor 
        //Object object為目標物件 
        //Method method為目標方法 
        //Object[] args 為引數, 
        //MethodProxy proxy CGlib方法代理物件 
        MethodInterceptor interceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object object, Method method, Object[] args,
                    MethodProxy proxy) throws Throwable {
                Object obj = null;
                if(name.equals("張三")){
                     obj = proxy.invokeSuper(object, args); ;
                }else{
                    System.out.println("----對不起,您沒有許可權----");
                }
                return obj;
            }
        };
        //NoOp.INSTANCE:這個NoOp表示no operator,即什麼操作也不做,代理類直接呼叫被代理的方法不進行攔截
        enhancer.setCallbacks(new Callback[]{interceptor,NoOp.INSTANCE});
        enhancer.setCallbackFilter(new CallbackFilter() {
            //過濾方法 
            //返回的值為數字,代表了Callback陣列中的索引位置,要到用的Callback 
            @Override
            public int accept(Method method) {
                if(method.getName().equals("select")){
                    return 0;
                }
                return 1;
            }
        });
        PeopleService ps = (PeopleService) enhancer.create();
        ps.add();

我們沒有修改一行原有的增刪改查程式碼,通過傳遞代理物件的方式輕鬆解決方法攔截、方法增強的業務需求。
但遺憾的是,cglib不支援android平臺。。。那我們就自己實現咯~在dexmaker和cglib-for-android庫的基礎上,修改部分程式碼後形成我們的類似cglib框架 MethodInterceptProxy ,實現上面需求只需這樣寫,和cglib寫法一致:

        final String name = "張五";
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PeopleService.class);
        //目標物件攔截器,實現MethodInterceptor 
        //Object object為目標物件 
        //Method method為目標方法 
        //Object[] args 為引數, 
        //MethodProxy proxy CGlib方法代理物件 
        MethodInterceptor interceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object object, Object[] args, MethodProxy methodProxy) throws Throwable {
                Object obj = null;
                if(name.equals("張三")){
                     obj = methodProxy.invokeSuper(object, args); ;
                }else{
                    System.out.println("----對不起,您沒有許可權----");
                }
                return obj;
            }
        };
        //NoOp.INSTANCE:這個NoOp表示no operator,即什麼操作也不做,代理類直接呼叫被代理的方法不進行攔截
        enhancer.setCallbacks(new MethodInterceptor[]{interceptor,NoOp.INSTANCE});
        enhancer.setCallbackFilter(new CallbackFilter() {
            //過濾方法 
            //返回的值為數字,代表了Callback陣列中的索引位置,要到用的Callback 
            @Override
            public int accept(Method method) {
                if(method.getName().equals("select")){
                    return 0;
                }
                return 1;
            }
        });
        PeopleService ps = (PeopleService) enhancer.create();
        ps.add();

專案地址:MethodInterceptProxy

相關文章