由swizzle想到j(luò)ava中的Aop

無(wú)論是在服務(wù)端開(kāi)發(fā)還是客戶(hù)端開(kāi)發(fā)很多基本的思想和設(shè)計(jì)模式都是想通。比如去年記得阿里開(kāi)源了BeeHive就是受了java中的Spring的思想,除此之外其實(shí)還有很多很多類(lèi)似由一門(mén)語(yǔ)言遷移到另一門(mén)語(yǔ)言的三方庫(kù)。

平時(shí)在看一些iOS三方源碼的時(shí)候發(fā)現(xiàn)都會(huì)用到swizzle技術(shù),今天就來(lái)一起看看在java中,或者說(shuō)andriod中通過(guò)什么方式可以達(dá)到類(lèi)似的效果。

讀完本文,你可以了解到:

  1. iOS中的代理和java中的代理是怎么回事
  2. java中的靜態(tài)代理與靜態(tài)代理的原理
  3. 面向切片(AOP)的思想
  4. Android中Aop和iOS中Aop的比較。

代理模式

關(guān)于什么是代理設(shè)計(jì)模式,這里就不多說(shuō)了??紤]到有些同學(xué)坑你還不是特別了解設(shè)計(jì)模式。那么如果想繼續(xù)讀下去還是去復(fù)習(xí)一下設(shè)計(jì)模式基礎(chǔ)知識(shí)會(huì)好一點(diǎn)。這里推薦C#可以看《大話設(shè)計(jì)模式》,java可以看《Java設(shè)計(jì)模式》。不用一一看完,大致了解一下,需要用得到時(shí)候在仔細(xì)看也不遲。

在iOS開(kāi)發(fā)中經(jīng)常會(huì)用到代理設(shè)計(jì)模式,代理解決的問(wèn)題當(dāng)兩個(gè)類(lèi)需要通信時(shí),引入第三方代理類(lèi),將兩個(gè)類(lèi)的關(guān)系解耦,讓我們只了解代理類(lèi)即可,而且代理的出現(xiàn)還可以讓我們完成與另一個(gè)類(lèi)之間的關(guān)系的統(tǒng)一管理。在iOS面試的時(shí)候經(jīng)常會(huì)問(wèn)的就是請(qǐng)介紹一下block、代理、通知三者之間的區(qū)別和聯(lián)系。

這里我用最為通俗的方式來(lái)解釋一下我對(duì)代理的理解:就是在原有的代碼里面打個(gè)標(biāo)記(調(diào)用一個(gè)函數(shù),這個(gè)函數(shù)一般情況下需要接口來(lái)約束一下。),當(dāng)走到這里的時(shí)候就去其他執(zhí)行代碼,那么以后如果有更為復(fù)雜的邏輯就可以在其他地方改了。這樣就達(dá)到一定的解耦。

目的一般就是:

  • 不修改已有代碼進(jìn)行功能增強(qiáng)。
  • 剔除方法中的非核心邏輯,精簡(jiǎn)代碼。

Java靜態(tài)代理

所謂靜態(tài)也就是在程序運(yùn)行前就已經(jīng)存在代理類(lèi)的字節(jié)碼文件,代理類(lèi)在運(yùn)行前就確定了。

例子

  • 定義代理接口 (類(lèi)似于OC中定義protocal):一般用于約束代理的行為
public interface UserManager {
    public void addUser(String userId);
}
  • 定義委托類(lèi)(類(lèi)似于OC中單獨(dú)用一個(gè)類(lèi)實(shí)現(xiàn)協(xié)議一個(gè)協(xié)議):具體處理業(yè)務(wù)
public class UserManagerImpl implements UserManager {
    public void addUser(String userId) {
        System.out.print("add User----UserManagerImpl");
    }
}
  • 靜態(tài)代理類(lèi) (OC中委托類(lèi)和代理類(lèi)一般是放在一個(gè)類(lèi)中,這點(diǎn)和java有比較大的差別)
public class UserManagerImplProxy implements UserManager {
    // 目標(biāo)對(duì)象
    private UserManager userManager;
    // 通過(guò)構(gòu)造方法傳入目標(biāo)對(duì)象
    public UserManagerImplProxy(UserManager userManager){
        this.userManager=userManager;
    }
   public void addUser(String userId) {
        // 調(diào)用實(shí)際處理業(yè)務(wù)的委托類(lèi),達(dá)到解耦的目的
        userManager.addUser(userId);
        System.out.println("add User--UserManagerImplProxy" + userId);
        // userManager.addUser(userId);
    }
}
  • 測(cè)試代碼
public static void main(String[] args) {
        UserManager userManagerImpl = new UserManagerImpl();
        UserManager userManagerImplProxy = new UserManagerImplProxy(userManagerImpl);
        userManagerImplProxy.addUser("1234");
    }

運(yùn)行結(jié)果:

add User----UserManagerImpl1234
add User--UserManagerImplProxy1234

其實(shí)上面的代碼有一點(diǎn)點(diǎn)Aop的雛形。也就是在不改變?cè)瓉?lái)代碼的情況下,增加新的功能。這里如果以后需要擴(kuò)展業(yè)務(wù)就不用改UserManagerImplProxy代碼了,直接在UserManagerImpl中修改業(yè)務(wù)邏輯就可以了。不知道自己講清楚沒(méi)有。

始終記住aop,或者代理的目的就是:

  • 不修改已有代碼進(jìn)行功能增強(qiáng)。
  • 剔除方法中的非核心邏輯,精簡(jiǎn)代碼。

缺點(diǎn)明顯

靜態(tài)代理在iOS其實(shí)用得非常之多,這里只是用java舉了個(gè)例。一個(gè)很明顯的缺點(diǎn)就是:

  • 代理類(lèi)和委托類(lèi)實(shí)現(xiàn)了相同的接口,代理類(lèi)通過(guò)委托類(lèi)實(shí)現(xiàn)了相同的方法。這樣就出現(xiàn)了大量的代碼重復(fù)。如果接口增加一個(gè)方法,除了所有實(shí)現(xiàn)類(lèi)需要實(shí)現(xiàn)這個(gè)方法外,所有代理類(lèi)也需要實(shí)現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度。

一個(gè)更為實(shí)際的例子,比如在iOS中要統(tǒng)計(jì)每個(gè)頁(yè)面停留的時(shí)間,不可能每個(gè)控制器都去統(tǒng)計(jì)吧;在java中如果想讓每個(gè)實(shí)現(xiàn)類(lèi)都添加打印日志的功能的話,就需要添加多個(gè)代理類(lèi),以及代理類(lèi)中各個(gè)方法都需要添加打印日志功能。

靜態(tài)代理最為嚴(yán)重的缺點(diǎn)就是:靜態(tài)代理類(lèi)只能為特定的接口(Service)服務(wù)。如想要為多個(gè)接口服務(wù)則需要建立很多個(gè)代理類(lèi)。

java動(dòng)態(tài)代理

動(dòng)態(tài)代理解決了靜態(tài)代理的缺點(diǎn),可以不與某個(gè)類(lèi)或接口強(qiáng)綁定。它的原理也是有點(diǎn)復(fù)雜的,大致過(guò)程就是程序運(yùn)行期間動(dòng)態(tài)的生成一個(gè)比特?cái)?shù)組,這個(gè)數(shù)組能夠表示為目標(biāo)類(lèi)的子類(lèi),然后把數(shù)組交給ClassLoader進(jìn)行解析,并返回子類(lèi)的實(shí)例,這個(gè)子類(lèi)的實(shí)例實(shí)際上可以看做目標(biāo)類(lèi)的代理類(lèi)。(有點(diǎn)類(lèi)似iOS中一個(gè)很著名的Aop框架Aspect的原理,都是在運(yùn)行的時(shí)候生成子類(lèi)。)

java能夠做到動(dòng)態(tài)代理因?yàn)?strong>Java是個(gè)編譯型的語(yǔ)言,首先會(huì)把Java代碼編譯成class字節(jié)碼,然后JVM去加載、解釋字節(jié)碼,通過(guò)ClassLoader類(lèi)可以在程序運(yùn)行期間動(dòng)態(tài)的加載字節(jié)碼生成一個(gè)類(lèi)。

java實(shí)現(xiàn)動(dòng)態(tài)代理有兩種方式,一種是JDK自帶的動(dòng)態(tài)代理,要求目標(biāo)類(lèi)必須實(shí)現(xiàn)一個(gè)接口InvocationHandler,接口方法就是增強(qiáng)方法;另一種是CGLIB動(dòng)態(tài)代理,要求目標(biāo)類(lèi)不能為final,否則不能生成子類(lèi)。今天講的就是用InvocationHandler實(shí)現(xiàn)動(dòng)態(tài)代理。

目前大致的如下方法實(shí)現(xiàn)Aop:

接口InvocationHandler

文檔里面這樣介紹的:

public interface InvocationHandler
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

大致意思就是:

每一個(gè)動(dòng)態(tài)代理類(lèi)都必須要實(shí)現(xiàn)InvocationHandler這個(gè)接口,并且每個(gè)代理類(lèi)的實(shí)例都關(guān)聯(lián)到了一個(gè)handler,當(dāng)我們通過(guò)代理對(duì)象調(diào)用一個(gè)方法的時(shí)候,這個(gè)方法的調(diào)用就會(huì)被轉(zhuǎn)發(fā)為由InvocationHandler這個(gè)接口的 invoke 方法來(lái)進(jìn)行調(diào)用。

invoke方法:Object invoke(Object proxy, Method method, Object[] args) throws Throwable。三個(gè)參數(shù)意思分別是

  • proxy:  指代我們所代理的那個(gè)真實(shí)對(duì)象
  • method:  指代的是我們所要調(diào)用真實(shí)對(duì)象的某個(gè)方法的Method對(duì)象
  • args:   指代的是調(diào)用真實(shí)對(duì)象某個(gè)方法時(shí)接受的參數(shù)

Proxy

文檔說(shuō)明:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

Proxy這個(gè)類(lèi)的作用就是用來(lái)動(dòng)態(tài)創(chuàng)建一個(gè)代理對(duì)象的類(lèi),它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個(gè)方法

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException參數(shù)說(shuō)明:

  • loader:  一個(gè)ClassLoader對(duì)象,定義了由哪個(gè)ClassLoader對(duì)象來(lái)對(duì)生成的代理對(duì)象進(jìn)行加載。
  • interfaces: 一個(gè)Interface對(duì)象的數(shù)組,表示的是我將要給我需要代理的對(duì)象提供一組什么接口,如果我提供了一組接口給它,那么這個(gè)代理對(duì)象就宣稱(chēng)實(shí)現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了。
  • h:  一個(gè)InvocationHandler對(duì)象,表示的是當(dāng)我這個(gè)動(dòng)態(tài)代理對(duì)象在調(diào)用方法的時(shí)候,會(huì)關(guān)聯(lián)到哪一個(gè)InvocationHandler對(duì)象上。

最后我們還是來(lái)看看實(shí)際例子

例子

接口還是用UserManager.

  • 定義代理接口:
public interface UserManager {
    public void addUser(String userId);
}
  • 定義委托類(lèi):
public class UserManageDynamicImpl implements UserManager {
    public void addUser(String userId) {
        System.out.println("UserManageDynamicImpl:   add User" + userId);
    }
}
  • 定義動(dòng)態(tài)代理類(lèi)(日志記錄)
public class LogInvocationHander implements InvocationHandler {
    // 引入日志框架
    private Logger logger = Logger.getLogger(this.getClass().getSimpleName());

    // 真實(shí)對(duì)象,目標(biāo)代理類(lèi)
    private Object target;
    // 代理類(lèi)
    private Object proxy;

    // 存儲(chǔ)日記記錄字典
    private static HashMap<Class<?>, LogInvocationHander> invoHandlers = new HashMap<Class<?>, LogInvocationHander>();

    // java中單例的寫(xiě)法
    private LogInvocationHander() {

    }

    /**
     * 類(lèi)方法
     * @param clazz
     * @param <T>
     * @return
     */
    public synchronized static <T> T  getProxyInstance(Class<T> clazz) {
        LogInvocationHander invoHandler = invoHandlers.get(clazz);

        if (invoHandler == null) {
            invoHandler = new LogInvocationHander();
            try {
                T tar  = clazz.newInstance();
                 // 設(shè)置目標(biāo)代理類(lèi)
                invoHandler.setTarget(tar);
                // Proxy這個(gè)類(lèi)的作用就是用來(lái)動(dòng)態(tài)創(chuàng)建一個(gè)代理對(duì)象的類(lèi),它提供了許多的方法,
                // 但是我們用的最多的就是 newProxyInstance 這個(gè)方法

                /*loader:  一個(gè)ClassLoader對(duì)象,定義了由哪個(gè)ClassLoader對(duì)象來(lái)對(duì)生成的代理對(duì)象進(jìn)行加載

                interfaces:  一個(gè)Interface對(duì)象的數(shù)組,表示的是我將要給我需要代理的對(duì)象提供一組什么接口,如果我提供了一組接口給它,那么這個(gè)代理對(duì)象就宣稱(chēng)實(shí)現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了

                h:  一個(gè)InvocationHandler對(duì)象,表示的是當(dāng)我這個(gè)動(dòng)態(tài)代理對(duì)象在調(diào)用方法的時(shí)候,會(huì)關(guān)聯(lián)到哪一個(gè)InvocationHandler對(duì)象上
                */
               // 設(shè)置代理類(lèi)
                invoHandler.setProxy(Proxy.newProxyInstance(tar.getClass().getClassLoader(),tar.getClass().getInterfaces(),invoHandler));

            } catch (Exception e) {
                e.printStackTrace();
            }

            invoHandlers.put(clazz, invoHandler);
        }

        return (T)invoHandler.getProxy();
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        return proxy;
    }

    public void setProxy(Object proxy) {
        this.proxy = proxy;
    }

    /**
     proxy:  指代我們所代理的那個(gè)真實(shí)對(duì)象
     method: 指代的是我們所要調(diào)用真實(shí)對(duì)象的某個(gè)方法的Method對(duì)象
     args:  指代的是調(diào)用真實(shí)對(duì)象某個(gè)方法時(shí)接受的參數(shù)
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //  在代理真實(shí)對(duì)象前我們可以添加一些自己的操作
        logger.info("before call");

        // 當(dāng)代理對(duì)象調(diào)用真實(shí)對(duì)象的方法時(shí),其會(huì)自動(dòng)的跳轉(zhuǎn)到代理對(duì)象關(guān)聯(lián)的handler對(duì)象的invoke方法來(lái)進(jìn)行調(diào)用
        Object result = method.invoke(target, args); // 執(zhí)行業(yè)務(wù)處理

        // 打印日志
        logger.info("____invoke method: " + method.getName()
                + "; args: " + (null == args ? "null" : Arrays.asList(args).toString())
                + "; return: " + result);

        //  在代理真實(shí)對(duì)象后我們也可以添加一些自己的操作
        logger.info("after call");

        return result;
    }
}

上面的代碼注釋已經(jīng)足夠說(shuō)明問(wèn)題了。還有一部分需要解釋下:

  • 通過(guò) Proxy.newProxyInstance 創(chuàng)建的代理對(duì)象是在jvm運(yùn)行時(shí)動(dòng)態(tài)生成的一個(gè)對(duì)象,它并不是我們的InvocationHandler類(lèi)型,也不是我們定義的那組接口的類(lèi)型,而是在運(yùn)行是動(dòng)態(tài)生成的一個(gè)對(duì)象,并且命名方式都是這樣的形式,以$開(kāi)頭,proxy為中,最后一個(gè)數(shù)字表示對(duì)象的標(biāo)號(hào)。

  • 通過(guò)代理對(duì)象來(lái)調(diào)用實(shí)現(xiàn)的那種接口中的方法,這個(gè)時(shí)候程序就會(huì)跳轉(zhuǎn)到由這個(gè)代理對(duì)象關(guān)聯(lián)到的 handler 中的invoke方法去執(zhí)行,而我們的這個(gè) handler 對(duì)象又接受了一個(gè) target類(lèi)型的參數(shù),表示我要代理的就是這個(gè)真實(shí)對(duì)象,所以此時(shí)就會(huì)調(diào)用 handler 中的invoke方法去執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,803評(píng)論 11 349
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,701評(píng)論 18 399
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李頭閱讀 3,395評(píng)論 2 10
  • 今年初三的早晨,天氣微寒。大哥、四弟和我?guī)е覍僖恍?人到禹縣爬山去。我們的設(shè)想是見(jiàn)山就爬,目的地是具茨山下的周定...
    秋山秋水閱讀 912評(píng)論 0 1
  • 說(shuō)起來(lái)有點(diǎn)自嘲,高中讀書(shū)時(shí)語(yǔ)文成績(jī)是最差的我,報(bào)考時(shí)卻神差鬼使地上了中文系學(xué)起了文學(xué)做了個(gè)中學(xué)語(yǔ)文教師。這一...
    易夢(mèng)的原野閱讀 358評(píng)論 4 4

友情鏈接更多精彩內(nèi)容