代理模式(动态代理)及其原理分析
概念
有一种设计模式叫做代理模式,其中也用到了动态代理。动态代理就是为某一个对象提供一个代理对象,已达到对这个对象的代理访问,最典型的例子就是“律师”和“普通民众”的关系。
代理模式
我们直接上一个简单的小例子来阐述这个模式。
事件:小明要上诉,他找了一个律师帮他。
这里是上诉的步骤
public interface ILawsuit {
/**
* 提交申请
*/
void submit();
}
接下来是小明要开始上诉,上诉的实现类
public class XiaoMin implements ILawsuit {
@Override
public void submit() {
System.out.println("我要上诉!");
}
}
接下来是律师实现类,他也要实现上诉的步骤
public class Lawyer implements ILawsuit {
//持有小明的引用
private ILawsuit mLawsuit;
public Lawyer(ILawsuit lawsuit) {
mLawsuit = lawsuit;
}
@Override
public void submit() {
//除了小明自己的上诉,我还可以加上一些属于律师的逻辑
mLawsuit.submit();
}
}
一个简单的代理模式就实现了,这里可以感受出来代理模式的一些好处了。方便后期逻辑拓展,以及简化了客户端(使用者)使用这个对象(小明)的逻辑。
但是在一个被代理对象有大量逻辑或者多个对象都需要代理的时候,我们不可能再去写一个大量逻辑的代理类了,不然也太麻烦了,因此Java就给我们了一个快速拿到代理对象的方法,此时就没有“律师”类了,请看下面的一个实现代理逻辑的类:
public class DynamicProxy implements InvocationHandler {
//代理的对象的引用
private Object object;
public DynamicProxy(Object o) {
object = o;
}
/**
* 实现方法的调用
* @param proxy 真实对象(小明)
* @param method 原方法的对象
* @param args 原方法的参数
* @return 原方法的返回结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//实现方法的调用
//每当代理对象调用一个方法的时候,最后都触发的是这一个方法
Object result = method.invoke(object, args);
return result;
}
}
在这个(动态代理)的类中,我们实现了一个接口,这个接口就是 InvocationHandler
,里面只有一个 invoke()
方法,这个方法就是被代理对象真正实现的地方,例如:小明调用上诉方法,真正执行的是method.invoke(object, args)
,这一句即是实现method方法。这个类的实现就让我们可以同时对多个代理逻辑相同的对象进行代理,而且这个类只将所有方法都集中在了一个invoke()
方法中,省去了大量重复代码,例如可以在这里添加打印日志的功能等。
接下来来看小明再次怎么被代理的:
//对真实对象的代理的逻辑实现
DynamicProxy proxy = new DynamicProxy(xiaoMin);
//定义了由哪个ClassLoader对象来对生成的代理对象进行加载
ClassLoader loader = xiaoMin.getClass().getClassLoader();
//这才是最终的代理对象
ILawsuit lawyer = (ILawsuit) Proxy.newProxyInstance(loader, new Class[]
{ILawsuit.class},proxy);
lawyer.submit();
这里我们关注一下Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h);
方法,先说三个参数:
- loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载。
- interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了。
- h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。
细心的你可能发现了不对的地方,我们拿到的lawyer
对象如果只是一个XiaoMin
对象的话,那么在调用submit()
方法的时候不可能触发invoke()
方法,而是直接实现。那么证实 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h);
返回的不是XiaoMin
对象,而是一个JVM根据ILawsuit接口、 xiaoMin对象、proxy动态代理对象
来生成的一个对象。
然后我去跟着这些方法去源码里面看一下,先看newProxyInstance()
方法,这个方法负责的就是生成代理类的对象:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException
{
//要求代理逻辑对象非空
Objects.requireNonNull(h);
//根据传入的类构造器和接口对象,查找或者生成指定的代理类
Class<?> cl = getProxyClass0(loader, intfs);
//调用构造函数
try {
//拿到含有InvocationHandler类型参数的构造方法
//目的是最后的类对象是跟我们的代理逻辑的对象相关联
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//生成的类的修饰符不是公开的话
if (!Modifier.isPublic(cl.getModifiers())) {
//这里其实是拿到报异常的
cons.setAccessible(true);
}
//通过反射拿到生成类的实例
return cons.newInstance(new Object[]{h});
}
}
其中的constructorParams
是来自private static final Class<?>[] constructorParams ={ InvocationHandler.class };
这一句话,也就是实现我们的代理逻辑的那个接口的一个数组对象,其实这里我们根据最后的返回值return cons.newInstance(new Object[]{h})
就应该了解到编译器为我们生成的类中的构造函数有一个是传入我们的代理逻辑对象(InvocationHandler
对象)的构造函数,因此实现了代理逻辑和生成类的关联,那么此时我们的生成类就包含了我们的被代理类(小明)的逻辑和我们(对小明)的代理逻辑。
接下来我们再点进去这个方法中的一个重要操作getProxyClass0(loader, intfs)
,经过两步操作,我们最终是来到了WeakCache(弱缓存)的public V get(K key, P parameter){}
方法:
/**
*V: 最后的代理类泛型对象
*key: classLoader(小明类对应的类加载器)
*parameter: 接口类(小明的申诉步骤接口)的数组对象
*/
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
//根据类加载器拿到一个从缓存读取数据的key
Object cacheKey = CacheKey.valueOf(key, refQueue);
// 从对应的类加载器中拿到的并行集合,存放的值是Supplier(接口“提供者”)
// map一开始是空的,map也是从这个方法里进行填充的
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
// 如果没有这个并行集合,则new 一个并行集合并放入这个map。
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// 创建子键,并从valueMap 中检索可能存在的对应子键的Supplier(提供者)
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
//工厂(Supplier的实现类)
Factory factory = null;
while (true) {
// 提供者不为空的话直接拿到我们需要的代理类的Class对象
// 返回一个值,结束该方法
if (supplier != null) {
V value = supplier.get();
if (value != null) {
return value;
}
}
// 实例出一个工厂对象(判断代理类就是在Factory中生成的)
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
//当提供者为空的时候
if (supplier == null) {
//如果并行集合中没得对应的提供者,则放入刚创建的factory(提供者)
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
supplier = factory;
}
} else {
// 不为空的话替换并行集合中对应的元素
if (valuesMap.replace(subKey, supplier, factory)) {
// 直接拿到提供者
supplier = factory;
} else {
// 根据子键拿出对应的提供者
supplier = valuesMap.get(subKey);
}
}
}
}
看到这个方法是不是脑袋都大了,没错,其实我的脑袋也大了,很多东西是懵懵懂懂的。但是我们依旧可以梳理一遍这个方法的大体逻辑:
我们为了拿到我们想要的代理类,我们是从弱缓存中去找,先根据我们的类加载器从一个map中去找,找的目标是也是一个集合,这是一个放了可以拿到我们想要的代理类的集合————一个并行集合。但是一般的话这个map都是空的,因为它是直接new 出来的(这个类的顶部有),里面的元素也是在这个方法中被放进去的,那么也就是说我们找到的并行集合也可能是个空的,对吧?所以的话我们也是直接new 一个并行集合进去的。那我们new 出来的并行集合也是空的啊,没关系,因为我们接下来往里面添加东西就对了。重点来了,我们的Factory类,它本身也是一个Supplier(接口) 的实现类,在实例化的过程,我们根据Factory构造器传入值,生成对应的代理类,并且factory对象自身会被放入缓存集合,以便下次直接从它拿到目标代理类。也就是说我们可以通过Factory直接生产出我们想要的代理类的,而这个方法中的其它逻辑则是为了让我们能够实现保存,在下一次便可以直接调用了,以达到节省资源的目的。
最后的最后,我们再去Factory中去看看吧,验证我们的猜想是否正确,我们去找我们想要的代码,
在这里我们保留了关键的代码:
private final class Factory implements Supplier<V> {
Factory(K key, P parameter, Object subKey,
ConcurrentMap<Object, Supplier<V>> valuesMap) {
this.key = key;
this.parameter = parameter;
this.subKey = subKey;
this.valuesMap = valuesMap;
}
@Override
public synchronized V get() {
// re-check
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
// 简单理解,防止意外发生,返回Null让其一直循环
return null;
}
// 激动的时候,这就是我们的目标代理类
V value = null;
try {
// 再深入就又要扯好远,这里又是另外的工厂类了,反正就是生产value!!!
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
}
//哈哈哈,拿到了!!!
return value;
}
}
这便是我们生产我们的目标代理类的全部过程啦,我们应该注意的是最后跟随我们到底的一直有我们的“申诉步骤”的接口,以确保我们拿到的是这个接口的对象,然后我们就可以执行里面的方法了。
我还在这里制作了一张简单的流程图:
由于笔者水平有限,我在找了大篇的博客后也没能找出JVM为我们生成的代理类,所以我们也只能估摸着这个代理类的作用了,就是在调用被代理对象方法的时候,记录该目标方法,并直到调用invoke()
方法中去,在invoke()
方法中我们自行实现目标方法的调用。
补充 —— AOP
翻译过来叫做“面向切面的编程”,简单说,就是找到一个模块的切入点,对它进行切面操作。
比如打Log这一操作吧,我们好多个对象、好多个方法都需要打上Log,那我们在每一个方法里面都打上自己的Log类的Log吧。首先,打这么多的Log不累吗?而且这么多Log肯定少不了大量重复代码。所以我们面对打Log这一功能进行处理,就用动态代理的形式来做吧,我给每一个对象一个代理,让它执行方法的时候都在这个代理对象中去执行,那么是不是就是可以在代理对象中去实现打Log 的功能了,这样就将Log和方法分开来了,简化了代码,增强了后期拓展性,而且代码变整洁了,有木有? 。
你可能会问了,我给每一个对象都搞一个代理是不是更麻烦哟?那么,我的第二个目的就是要简化这一实现过程了,是不是?例如Retrofit 第三方库就有采用一种方法————利用Apt(编译时扫描和处理注解),使用的时候在需要代理的对象上面加上简单的注解就实现了这一功能,在这里就不详细解释如何实现的了。