1. 程式人生 > >Java 動態代理作用是什麼?為什麼要動態代理

Java 動態代理作用是什麼?為什麼要動態代理

作者:Intopass
連結:https://www.zhihu.com/question/20794107/answer/75164285
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

① 首先你要明白靜態代理的作用
我們有一個字型提供類,有多種實現(從磁碟,從網路,從系統)

public interface FontProvider {
    Font getFont(String name);
}

public abstract class ProviderFactory {
    public static FontProvider getFontProvider() {
        return new FontProviderFromDisk();
    }
}

public class Main() {
    public static void main(String[] args) {
        FontProvider fontProvider = ProviderFactory.getFontProvider();
        Font font = fontProvider.getFont("微軟雅黑");
        ......
    }
}

現在我們希望給他加上一個快取功能,我們可以用靜態代理來完成

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 abstract class ProviderFactory {
    public static FontProvider getFontProvider() {
        return new CachedFontProvider(new FontProviderFromDisk());
    }
}

當然,我們直接修改FontProviderFromDisk類也可以實現目的,但是我們還有FontProviderFromNet, FontProviderFromSystem等多種實現類,一一修改太過繁瑣且易出錯。
況且將來還可能新增日誌,許可權檢查,異常處理等功能顯然用代理類更好一點。

② 然而為什麼要用動態代理?
考慮以下各種情況,有多個提供類,每個類都有getXxx(String name)方法,每個類都要加入快取功能,使用靜態代理雖然也能實現,但是也是略顯繁瑣,需要手動一一建立代理類。

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

使用動態代理怎麼完成呢?

public class CachedProviderHandler implements InvocationHandler {
    private Map<String, Object> cached = new HashMap<>();
    private Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
        Type[] types = method.getParameterTypes();
        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 abstract class ProviderFactory {
    public static FontProvider getFontProvider() {
        Class<FontProvider> targetClass = FontProvider.class;
        return (FontProvider) Proxy.newProxyInstance(targetClass.getClassLoader(),
            new Class[] { targetClass },
            new CachedProviderHandler(new FontProviderFromDisk()));
    }
}

③ 這也是為什麼Spring這麼受歡迎的一個原因
Spring容器代替工廠,Spring AOP代替JDK動態代理,讓面向切面程式設計更容易實現。
在Spring的幫助下輕鬆新增,移除動態代理,且對原始碼無任何影響。