由浅入深完全理解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中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值