由淺入深完全理解Java動態代理
前言
看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中。
相關文章
- 由淺入深理解 IOC 和 DI
- 由淺入深理解Dubbo的SPI機制
- promise由淺入深Promise
- MongoDB由淺入深MongoDB
- javascript由淺入深JavaScript
- JavaScript Promise由淺入深JavaScriptPromise
- MySQL索引由淺入深MySql索引
- Java 反射由淺入深 | 進階必備Java反射
- 前端如何理解正則-由淺入深的學習前端
- 輕鬆理解 Java 靜態代理/動態代理Java
- 物件導向-由淺入深物件
- 對JAVA動態代理的理解Java
- 純手寫Promise,由淺入深Promise
- Vue.js 2.0 由淺入深Vue.js
- iOS架構由淺入深 | MVVMiOS架構MVVM
- Git由淺入深之分支管理Git
- 由淺入深學python(一)Python
- Git由淺入深之操作與指令Git
- Java 靜態代理和動態代理的使用及原理解析Java
- 深入理解 Java 反射和動態代理Java反射
- 由淺入深 docker 系列: (6) 映象分層Docker
- 由淺入深 docker 系列: (2) docker 構建Docker
- 由淺入深學C# 視訊教程C#
- 由淺入深學習JavaScript Debug技巧JavaScript
- java基礎:深入理解JDK動態代理JavaJDK
- Git 由淺入深之細說變基 (rebase)Git
- 由淺入深 docker 系列: (5) 資源隔離Docker
- 由淺入深 docker 系列: (3) docker-composeDocker
- 第十八節:Skywalking由淺入深
- MySQL 頁完全指南——淺入深出頁的原理MySql
- 零基礎深度學習入門:由淺入深理解反向傳播演算法深度學習反向傳播演算法
- 由淺入深 學習 Android Binder(三)- java binder深究(從java到native)AndroidJava
- 靜態代理、動態代理與Mybatis的理解MyBatis
- 由淺入深講解責任鏈模式,理解Tomcat的Filter過濾器模式TomcatFilter過濾器
- Java代理(jdk靜態代理、動態代理和cglib動態代理)JavaJDKCGLib
- java靜態代理和動態代理Java
- java動態代理Java
- 深入理解動態代理