模式介绍
代理模式(Proxy Design Pattern)是一种结构型设计模式,它用于在不更改原有类代码的情况下,扩展(增强)其功能。
代理模式给某一个对象提供一个代理对象,由代理对象持有原对象的引用,并控制对原有对象的访问。通过对象组合代理对象可以完全覆盖原对象的能力,并且可以按需增加相关代码,对功能进行增强,也使得代码更加符合OCP。
对于代理模式,Java开发者肯定不陌生,Spring、Mybatis、Dubbo等框架中都有大量使用,熟练掌握代理模式的设计思想与使用方式,对于我们阅读这些优秀的源码大有裨益。
下图为代理模式的UML类图,类图中的几个主体分别是客户端-Client、主题接口-Subject、实际主题实现类-RealSubject、代理类-Proxy。
从类图中可以看到:RealSubject、Proxy都实现了Subject接口,并且Proxy持有Subject的引用(实际为RealSubject实例);Client持有Subject的实例,但是该实例由运行时决定(Client持有的Subject实现为Proxy实例)。
Proxy类是一个中间层,原来由RealSubject直接完成的工作转交给Proxy去完成,充当了一个“中介”角色。这个中间层并不是徒增代码的复杂度,借助它我们可以做很多工作,也可以带来很多好处,通过几个业务场景举例说明一下:
- 如果需要统计RealSubject#request方法的耗时、错误率等指标,就可以在Proxy中做数据埋点而不需要修改RealSubjce的代码。统计性能指标本不是RealSubject的功能,强行为他增加逻辑会破坏它的完整性,不符合OCP、SRP等设计原则;而且如果RealSubject是第三方提供的服务,我们也无法更改其代码。使用Proxy代理类,更好的实现职责分离,原有代码无入侵,他好我也好。
- 如果需要为RealSubject#request方法增加Redis缓存以提高接口性能,我们可以使用Proxy类对用户的请求进行拦截,增加Redis缓存逻辑。当然在Java中,我们一般使用Spring AOP做动态实现,但是原理是一样的。
在实际应用中,代理模式有静态代理和动态代理两种实现。静态代理即代理类在代码编译之前已经实现,或为程序员手动实现,或为工具自动生成;动态代理是通过反射技术在运行时生成代理类。
上面文字描述比较枯燥,下面以示例代码对两种代理模式的使用进行说明。继续上文提到的例子,我们需要对现有业务代码增加性能统计逻辑,现有代码如下:
public interface Subject {
/**
* 接口方法
*/
void request();
}
public class RealSubject implements Subject {
@Override
public void request() {
try {
//通过sleep模拟业务执行
Thread.sleep(50);
} catch (Exception ex) {
}
System.out.println("RealSubject#request method invoked.");
}
}
public class Client {
public static void main(String[] args) {
//实例化对象
Subject realSubject=new RealSubject();
//方法调用
realSubject.request();
}
}
静态代理
目标:为RealSubject#request增加性能统计功能,为了简单我们仅做耗时演示。
思路:按照代理模式的设计思想,在静态代理中我们需要手动创建代理类,该代理类需要同样实现Subject接口,并且包含对RealSubject的引用,需要在代理类中增加调用耗时计算逻辑用于完成耗时统计。
基于上述思路,创建MetricProxy代理类,代码如下:
public class MetricProxy implements Subject {
private Subject delegate;
public MetricProxy(Subject delegate) {
this.delegate = delegate;
}
@Override
public void request() {
//方法执行开始时间
long start = (new Date()).getTime();
//调用委托对象的request方法
this.delegate.request();
//方法执行结束时间
long stop = (new Date()).getTime();
//计算耗时
long span = stop - start;
System.out.printf("invoke request() cost %d ms", span);
}
}
通过MetricProxy构造方法传入委托对象,然后在request方法内调用delegate#request方法,确保原有功能不变;为了增加耗时统计,在调用delegate#request前后增加了时间记录和计算逻辑。接下来,我们就可以在Client中这么调用了:
public class Client {
public static void main(String[] args) {
//创建委托对象
Subject delegate = new RealSubject();
//创建代理对象
Subject proxy = new MetricProxy(delegate);
//调用代理对象request方法
proxy.request();
}
}
整体看来,静态代理方式是“模式介绍”一节中的UML类图的“高保真”实现,两者结合起来,对于代理模式应该会有一个直观的理解了。
这里有个问题需要一起考虑下:**现在是一个Subject接口需要增加性能统计功能,如果有100个Subject这样的类似的接口需要扩展统计功能呢?写100个代理类?简直不要太酸爽!**到这里,就需要动态代理上场了。
动态代理
前面有提到,动态代理是在运行时使用反射技术动态实现代理类,开发人员对这一过程并无感知。Java SDK reflect包提供了Proxy、InvocationHandler等工具类用于实现动态代理,使用Java实现动态代理并不是难题。
按照JDK要求创建代理助手类DynamicProxyHandler,实现InvocationHandler接口,在其invoke方法内实现性能指标统计功能。
public class DynamicProxyHandler implements InvocationHandler {
//委托对象
private Object delegate;
public DynamicProxyHandler(Object delegate) {
this.delegate = delegate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//开始执行时间
long start = (new Date()).getTime();
//调用委托对象的方法
Object result = method.invoke(delegate, args);
//调用完成时间
long stop = (new Date()).getTime();
//计算时长
long span = stop - start;
System.out.printf("invoke request() cost %d ms", span);
return result;
}
}
使用Proxy#newProxyInstance创建动态代理对象:
public class Client {
public static void main(String[] args) {
//加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
//System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//创建代理助手类对象
DynamicProxyHandler proxy = new DynamicProxyHandler(new RealSubject());
//动态创建代理类对象,它会实现Subject接口
Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, proxy);
//调用方法
subject.request();
}
}
运行一下,会发现与静态代理的例子输出的结果完全一致。到这里,我们会感觉与静态代理没有什么差别,也实现了一个代理类,工作一点也没少。但是,仔细观察会发现DynamicProxyHandler其实是与类型无关的,这里我们传入了Subject,也同样可以传递其他的类型,这就是它的强大之处。
有没有好奇,JDK动态生成的代理类到底长什么样子呢?另外,有的同学会问subject.request()这句代码是如何走到DynamicProxyHandler#invoke的呢?
看下上面Client#main方法的第4行,把前面注释打开,再次运行代码,会在包com.sun.proxy下生成一个$Proxy0.class文件,我把它贴在下面:
public final class $Proxy0 extends Proxy implements Subject {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void request() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//反射创建方法对象
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.raysonxin.proxy.staticproxy.Subject").getMethod("request");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
$Proxy0继承Proxy类,并实现Subject接口,所以它同时具备两者的能力。以request方法为例:当request方法被调用时,它会执行super.h.invoke(this, m3, (Object[])null);
,这里的super是其基类Proxy,h就是我们的DynamicProxyHandler,所以最终会调用到DynamicProxyHandler#invoke方法。
Client中动态代理实现方式是JDK基础实现,而实际开发中往往借助Spring AOP强大的面向切面编程,基于Spring Bean、注解等技术进行动态实现。在我的上一篇文章《Mybatis源码之SQL执行过程》中,Mapper实例的创建就是一个鲜活的例子。
总结
代理模式是我们开发中常见、常用、常说的经典设计模式,旨在提高程序的可扩展性,让代码可维护。日常工作中使用的Spring、Mybatis等优秀的Java开发框架正是以反射+动态代理为基石,提供强大的功能,为我们的开发提供诸多便利。
本文从简单介绍了代理模式的设计思想,并通过实例对其经典的静态代理、动态代理两种方式举例说明。通过查看JDK生成的代理类,理解其执行过程。
百闻不如一见,百看不如一练。希望此文可以帮你初步了解代理模式。
本人能力有限,如有任何问题,请大家不吝赐教!