设计模式系列文章目录导读:
设计模式 ~ 面向对象 6 大设计原则剖析与实战
设计模式 ~ 模板方法模式分析与实战
设计模式 ~ 观察者模式分析与实战
设计模式 ~ 单例模式分析与实战
设计模式 ~ 深入理解建造者模式与实战
设计模式 ~ 工厂模式剖析与实战
设计模式 ~ 适配器模式分析与实战
设计模式 ~ 装饰模式探究
设计模式 ~ 深入理解代理模式
设计模式 ~ 小结
本文主要内容:
代理模式是使用非常广泛的一种设计模式,是一项基本的设计技巧,其他的模式诸如状态模式、策略模式、访问者模式本质上也采用了代理模式。
代理模式定义:为其他对象提供一种代理以控制对该对象的访问
生活中的代理也有很多,比如小商贩进货一般是向代理商拿货,而不是跑到生产厂家那里拿货;还有公司注册,有的自己懒得跑,交给专门中介公司处理,这些都是典型的代理模式的场景。
代理模式主要由以下 3 个角色组成:
抽象主题(Subject)角色
该角色是真是主题和代理主题的共同接口,以便在任何使用真实主题的地方都可以使用代理主题
代理主题(Proxy Subject)角色
也叫委托类、代理类,该角色负责控制对真实主题的引用,负责在需要的时候创建或删除真实主题对象,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
真实主题(Real Subject)角色
该角色也叫做被委托、被代理角色,是业务逻辑的具体执行者
代理模式的类图如下所示:
从上面的类图中我们可以发现代理角色
和 真是主题角色
都实现了相同的接口也就是抽象主题角色
,也就是真实主题出现的地方都可以使用代理角色
代理模式的实现方法主要有两种:静态代理和动态代理。
根据上面的类图和代理模式角色的介绍,我们来实现一个注册公司的逻辑:有个人想要注册公司,但是自己不愿意跑工商局,然后将这个工作交给了中介公司处理:
注册公司的业务进行抽象(抽象主题)
public interface IBusiness {
void registerCompany();
}
政府工商部门(真实主题)
// 政府工商部门
public class GovDepartment implements IBusiness {
@Override
public void registerCompany() {
System.out.println("----注册公司----");
}
}
中介公司(代理主题)
// 代理商
public class Agent implements IBusiness {
private IBusiness business;
public Agent() {
this.business = new GovDepartment();
}
@Override
public void registerCompany() {
before();
business.registerCompany();
after();
}
private void before() {
System.out.println("整理相关资料...");
}
private void after() {
System.out.println("邮寄证件给客户...");
}
}
测试
// 客户
public class Client {
public void registerCompany(){
IBusiness agent = new Agent();
agent.registerCompany();
}
public static void main(String[] args) {
new Client().registerCompany();
}
}
// 输出结果:
整理相关资料...
----注册公司----
邮寄证件给客户...
上面介绍完了动态代理,我们接下来看下什么是动态代理
从上面的静态代理中可以看出,代理类在执行真是主题的逻辑前后都加上了 “预处理”
和 “善后处理”
“预处理”
为 整理相关资料
“善后处理”
为 邮寄证件给客户
如果系统中其他地方也需要这个相同的逻辑怎么办?比如:落户
,例如你想落户到所在地城市,但是由于工作忙,不想理会这么多的乱七八糟的事情,可以把这个事情交给专门处理落户的中介公司。这个和上面的注册公司有着相同的预处理
和善后处理
逻辑:执行落户前都需要整理客户的相关资料,落户完成后处理好善后工作。
// 专门处理落户的中介公司
public class Agent implements ISettle {
// 真实主题
private GovDepartment department;
public Agent() {
this.department = new GovDepartment();
}
@Override
public void settle() {
System.out.println("整理相关资料");
department.settle();
System.out.println("邮寄资料给客户");
}
}
为了完成需求,我们需要和注册公司代理
一样,加上相同的 “预处理”
和 “善后处理”
逻辑。
经过分析我们发现 “预处理”
和 “善后处理”
逻辑是一模一样的,只是真实主题
的逻辑不一样而已,那能不能通过一个模板把变化的部分通过占位符
来表示?如:
before();
placeholder(); // 变化的部分
after();
如果有了上面这个类似模板占位符,那是不是静态代理中的代理类都不需要了呢?
是的,动态代理
就是用来解决这个问题的,动态代理顾名思义就是运行时的时候动态生成代理类(代理主题)
JDK
为我们提供了创建动态代理的 API:Proxy.newProxyInstance(...)
,通过 newProxyInstance
方法创建代理类,该方法的声明如下所示:
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h)
下面我们来简要分析下该方法的主要参数:
ClassLoader loader
该参数用于定义代理类的类加载器
Class>[] interfaces
该参数用于代理需要实现的接口
InvocationHandler h
该参数就上面我们分析的模板占位符,模板占位符定义在它的 invoke
方法里,调用代理类的方法时都会调用它的 invoke
方法
下面我们通过 动态代理
实现上面提到的 落户
逻辑:
落户逻辑的抽象(抽象主题)
public interface ISettle {
void settle();
}
政府部门处理落户(真实主题)
// 政府部门处理落户(真实主题)
public class GovDepartment implements ISettle {
@Override
public void settle() {
System.out.println("----落户----");
}
}
定义 InvocationHandler 模板占位符
public class ProxyHandler implements InvocationHandler {
private Object target;
// 真实主题通过构造方法传递进来
public ProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("整理相关资料");
// 调用真实主题的方法
Object result = method.invoke(target, args); //placeholder
System.out.println("邮寄资料给客户");
return result;
}
}
测试动态代理
public class Client {
public static void main(String[] args) {
// 真实主题传递ProxyHandler
ProxyHandler handler = new ProxyHandler(new GovDepartment());
ISettle business = (ISettle) Proxy.newProxyInstance(
ISettle.class.getClassLoader(),
new Class[]{ISettle.class},
handler);
business.settle();
}
}
// 输出结果:
整理相关资料
----落户----
邮寄资料给客户
可以看到虽然我们没有定义代理类(代理类由JDK动态生成),也达到了想上面静态代理一样的功能。同时功能更加强大,我们可以把上面的 ProxyHandler
应用到 公司注册
的逻辑中:
// 真实主题传递ProxyHandler
ProxyHandler handler = new ProxyHandler(new proxy.GovDepartment());
IBusiness business = (IBusiness) Proxy.newProxyInstance(
IBusiness.class.getClassLoader(),
new Class[]{IBusiness.class},
handler);
business.registerCompany();
// 输出结果:
整理相关资料
----注册公司----
邮寄资料给客户
我们可以将 ProxyHandler
应用到有着相同“预处理”
和“善后处理”
的类中,而不用每个都写一遍相同的逻辑。这就是典型的面向切面变成(AOP)
。
上面介绍完了动态代理,接下来我们来看下 JDK
中的动态代理底层是怎么实现的。
首先我们从 Proxy.newProxyInstance
作为入口开始分析,为了减少篇幅只保留该方法的核心流程:
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 省略其他代码...
// 获取或生成代理类
Class> cl = getProxyClass0(loader, intfs);
try {
// 代理类的构造方法
final Constructor> cons = cl.getConstructor(constructorParams);
// 通过反射返回代理实例
// 将 InvocationHandler 传递代理类构造方法
return cons.newInstance(new Object[]{h});
} catch () {
// 省略一系列的异常处理
}
}
上面的注释已经很详细,就不在一一赘述了。我们从上面的源码中发现 getProxyClass0
方法,该方法用来获取或者生成代理类:
private static Class> getProxyClass0(ClassLoader loader,
Class>... interfaces) {
// 接口数量超过 65535 抛出异常
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 从 proxyClassCache 获取代理类
return proxyClassCache.get(loader, interfaces);
}
来看下 proxyClassCache
是什么:
// WeakCache 就是一个代理类的缓存容器
// 构造该对象的时候传递了容器的KeyFactory,用于生成key
// 还有Value工厂,用于生成代理类
private static final WeakCache[], Class>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
然后我们在回到 proxyClassCache.get
也就是 WeakCache
的 get
方法:
public V get(K key, P parameter) {
// 省略其他代码...
//valuesMap 是 WeakCache 容器的 Value
//valuesMap 也是一个容器
// 获取代理类对应的key
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// 获取代理类
Supplier supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
// 由于是个 while 循环,直到 supplier 不为空
// Factory 实现了 Supplier
if (supplier != null) {
// supplier 有可能是 Factory 或 CacheValue
// 如果是 supplier 是 CacheValue 直接返回代理类
// 如果是 supplier 是 Factory 会生成代理类
V value = supplier.get();
if (value != null) {
// 返回代理类
return value;
}
}
// 省略其他代码...
}
}
上面的核心逻辑在于 while
循环中的 supplier.get()
,supplier
如果是 CacheValue
,说明已经缓存好了代理类, 第一次肯定没有缓存,这个时候时候 supplier
就是 Factory
,Factory
实际上是 WeakCache
的内部类,精简代码逻辑如下:
private final class Factory implements Supplier {
// 省略其他代码...
@Override
public synchronized V get() { // serialize access
V value = null;
try {
// 核心代码 valueFactory.apply
// valueFactory 就是 ProxyClassFactory
// ProxyClassFactory.apply 生成代理类
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
// 省略其他代码...
return value;
}
}
所以具体生成代理类的逻辑在 ProxyClassFactory.apply
方法中:
private static final class ProxyClassFactory
implements BiFunction[], Class>>
{
// 代理类名的前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 代理类名的编号(如果有多个进行累加)
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class> apply(ClassLoader loader, Class>[] interfaces) {
// 省略相关代码...
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
long num = nextUniqueNumber.getAndIncrement();
// 组装代理类的名称
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 生成Class字节码数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 将Class字节数组转化成 Class 对象
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
至此,我们就分析完毕从 Proxy.newProxyInstance
开始一步一步怎么触发生成代理类的,newProxyInstance
方法返回的就是代理类的 Class
,我们通过如下代码可以保存生成的代理类:
public static void main(String[] args) {
ProxyHandler handler = new ProxyHandler(new GovDepartment());
// com.sun.proxy 包下生成代理类
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
ISettle business = (ISettle) Proxy.newProxyInstance(
ISettle.class.getClassLoader(),
new Class[]{ISettle.class},
handler);
business.settle();
}
运行后就会在工程的根目录下生成 com.sun.proxy.$Proxy0.class
:
// 生成的代理类实现了 抽象主题接口
public final class $Proxy0 extends Proxy implements ISettle {
private static Method m1;
private static Method m2;
private static Method m0;
private static Method m3;
// 构造方法参数为 InvocationHandler
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
//====================Object方法 start==================================
// h 就是 InvocationHandler 对象
// 因为生成的 Proxy 类也是继承自 Object
// 调用 equals/toString/hashCode 也会调用 InvocationHandler 的 invoke 方法
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 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);
}
}
//====================Object方法 end==================================
// 调用 settle 方法,实际上是调用 InvocationHandler.invoke 方法
public final void settle() throws {
try {
// h 就是 InvocationHandler 对象
super.h.invoke(this, m3, (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");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("proxy.jdk.ISettle").getMethod("settle");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
至此,就把整个 JDK
中的动态代理分析完毕了。从中我们发现 JDK
中的动态代理是 基于接口
来实现的,由于 真实主题角色
可能实现多个接口,所以 newProxyInstance
方法的第二个参数是一个数组,数组中有多少个接口,动态生成的 Proxy
类就会实现多少个接口。可见 真实主题角色
和 动态生成的代理角色
实现的接口是保持一致的。其实动态代理和静态代理本质上是一模一样的,区别在于代理角色是动态生成的还是事先定义好的。
JDK
中的动态代理是基于接口
来实现的,如果不想通过接口的方式来实现动态代理,也可以通过CGLIB
来实现,它是基于继承
的,具体的相关资料读者可以查阅相关资料了解。
代理模式
中 代理角色
和 真实主题角色
接口保持一致; 装饰模式
中 具体装饰角色
和 具体构件
接口也是保持一致的。这两种模式也很容易混淆。
装饰模式
是用于为所装饰的对象增强功能,而代理模式是对对象的使用施加控制,并不提供对象本身的增强。看了很多模式相关的书籍,都是这么来区分代理模式和装饰模式的,但是读者还是不能很好的来区分它们之间的区别。比如说,我们常用代理模式来统计方法的执行效率或者方法的打点统计,那你说这是功能增强还是控制对象的访问呢?这里并没有控制对象的访问,对象的方法该怎么执行还是怎么执行,这好像是功能的增强啊,那这就是装饰模式吗?所以通过对象的功能增强还是对象的访问控制来区分并不好区分,而且代理模式用的时候也有可能不是来控制对象的访问的。
装饰模式中,会将具体构件通过构造方法传递给装饰器的,而代理模式中,可以将真实主题在代理类中创建,可以不用通过构造方法传递进来。如果装饰模式中如果具体构件在装饰器内部创建的话,装饰器的功能就大大减弱了,一般也不会这么做。所以说装饰模式更像是更加严苛的代理模式。除此之外,其他地方基本上是一样的。
另外本文涉及到的代码都在我的 AndroidAll GitHub 仓库中。该仓库除了 设计模式
,还有 Android 程序员需要掌握的技术栈,如:程序架构、设计模式、性能优化、数据结构算法、Kotlin、Flutter、NDK,以及常用开源框架 Router、RxJava、Glide、LeakCanary、Dagger2、Retrofit、OkHttp、ButterKnife、Router 的原理分析 等,持续更新,欢迎 star。