由淺入深完全理解Java動態代理

許佳佳233發表於2018-05-28

前言

看Retrofit原始碼的時候涉及到了Java動態代理,這個知識點之前在學習Java反射的時候就碰到過,不過也僅僅是停留在理論的學習。終於在Retrofit原始碼的時候看到了實際的使用,也是格外興奮,正好以此為契機,把Java動態代理這一塊整理一下。
本文也是儘量由淺入深的講,過多理論知識也不多加闡述。

本文參考
https://www.zhihu.com/question/20794107/answer/75164285

場景

我們有一個字型提供類,有多種實現(從磁碟,從網路,從系統)。
最簡單的情況下,我們會這麼寫。

public interface FontProvider {
    Font getFont(String name);
}
public class View {
    Font font;
    View(){
        this.font=new FontProviderFromDisk().getFont("something");
    }
}

但是,這麼寫會導致fontProvider只要改變,我們就需要更改View的程式碼。
因此耦合度太高了,我們可以使用代理模式去掉耦合。

靜態代理模式

public interface FontProvider {
    Font getFont(String name);
}
public class ResourceProvider {
    public static FontProvider getFontProvider() {
        return new FontProviderFromDisk();
    }
}

public class View {
    Font font;
    View(){
        this.font=ResourceProvider.getFontProvider().getFont("something");
    }
}

可以看到,我們把FontProvider的建立工作交給了ResourceProvider,這樣如果FontProvider的建立有更改,我們也不需要去修改View的程式碼。

動態代理模式

現在需求繼續增多,我們不僅僅要文字提供,還需要圖片提供,音樂提供等等,如果使用靜態代理,程式碼如下。

public class ResourceProvider {
    public static FontProvider getFontProvider() {...}
    public static ImageProvider getImageProvider() {...}
    public static MusicProvider getMusicProvider() {...}
    ......
}

在靜態代理的情況下,我們需要再去新建兩個類。
三者程式碼程式碼邏輯機會一模一樣,就是獲取到Provider,然後根據Provider獲取到內容。
現在僅僅只有三個,如果有十個種類提供呢?難道我們也重新新建十個的類嗎?
既然邏輯都一樣,那我們能不能將其統一。
這裡就可以使用動態代理實現。

public class ProviderHandler implements InvocationHandler{
    private Object target;

    public ProviderHandler(Object target){
        this.target=target;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        return method.invoke(target, args);
    }
}

public class ResourceProvider {

    public static FontProvider getFontProvider() {
        Class<FontProvider> targetClass = FontProvider.class;
        FontProvider fonProvider=(FontProvider)Proxy.newProxyInstance(
                targetClass.getClassLoader(),
                new Class[] { targetClass },
                new ProviderHandler(new FontProviderFromDisk()));
        return fonProvider;
    }
}

public class View {
    Font font;
    View(){
        this.font=ResourceProvider.getFontProvider().getFont("something");
    }
}

可以看到View的程式碼是不變的,不管是什麼代理模式都能實現解耦的作用。
ProviderHandler:
通過這個類可以通過invoke建立某個類。invoke的三個引數分別為:用來代理例項,要建立的類的類型別,引數(這裡指構造方法的引數)。
ResourceProvider:
通過使用ProviderHandler建立fontProvider。

單單動態代理的概念,看完上面這些基本就可以理解了,接下來我們在提供字型的基礎上加上快取,看一看要如何操作。
這邊主要是結合Java反射的使用。

新增快取

靜態代理

public interface FontProvider {
    Font getFont(String name);
}
public class CachedFontProvider implements FontProvider {
    private FontProvider fontProvider;
    private Map<String, Font> cached;

    public CachedFontProvider(FontProvider fontProvider) {
        this.fontProvider = fontProvider;
    }

    public Font getFont(String name) {
        Font font = cached.get(name);
        if (font == null) {
            font = fontProvider.getFont(name);
            cached.put(name, font);
        }
        return font;
    }
}
public class ResourceProvider {

    public static FontProvider getFontProvider() {
         return new CachedFontProvider(new FontProviderFromDisk());
    }
}

public class View {
    Font font;
    View(){
        this.font=ResourceProvider.getFontProvider().getFont("something");
    }
}

這裡主要新增了一個CachedFontProvider,這個Provider構造的時候會傳入其他文字的Provider,可以是硬碟的,記憶體的等。就是簡單的通過HashMap來實現了快取。
ResourceProvider 中使用CachedFontProvider就能讓文字擁有快取的功能了。
當然也可以看到,View的程式碼還是不會變的,因為這塊邏輯已經和View本身解耦了。

動態代理

public interface FontProvider {
    Font getFont(String name);
}
public class CachedFontProvider implements FontProvider {
    private FontProvider fontProvider;
    private Map<String, Font> cached;

    public CachedFontProvider(FontProvider fontProvider) {
        this.fontProvider = fontProvider;
    }

    public Font getFont(String name) {
        Font font = cached.get(name);
        if (font == null) {
            font = fontProvider.getFont(name);
            cached.put(name, font);
        }
        return font;
    }
}
public class ProviderHandler implements InvocationHandler{
    private Map<String, Object> cached = new HashMap<>();
        private Object target;

        public ProviderHandler(Object target) {
            this.target = target;
        }

        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
            //這裡method其實就是建構函式
            //獲取函式的引數型別
            Type[] types = method.getParameterTypes();
          //這裡限定引數只有一個,並且型別是String
            if (method.getName().matches("get.+") && (types.length == 1) &&
                    (types[0] == String.class)) {
                String key = (String) args[0];
                Object value = cached.get(key);
                if (value == null) {
                    value = method.invoke(target, args);
                    cached.put(key, value);
                }
                return value;
            }
            return method.invoke(target, args);
        }
}
public class ResourceProvider {

    public static FontProvider getFontProvider() {
        Class<FontProvider> targetClass = FontProvider.class;
        FontProvider fontProvider=(FontProvider)Proxy.newProxyInstance(
                targetClass.getClassLoader(),
                new Class[] { targetClass },
                new ProviderHandler(new FontProviderFromDisk()));
        return fontProvider; 
    }
}
public class View {
    Font font;
    View(){
        this.font=ResourceProvider.getFontProvider().getFont("something");
    }
}

這裡動態代理其實就是解決如何動態控制快取的問題。
與靜態代理相比,其實就是新增了ProviderHandler,然後ResourceProvider由本來的靜態建立變成了使用ProviderHandler動態建立。
ProviderHandler:
使用反射,根據函式型別進行篩選。然後根據對應的建構函式建立物件,並把這個物件快取到cache中。

相關文章