设计模式 ~ 深入理解代理模式

设计模式系列文章目录导读:

设计模式 ~ 面向对象 6 大设计原则剖析与实战
设计模式 ~ 模板方法模式分析与实战
设计模式 ~ 观察者模式分析与实战
设计模式 ~ 单例模式分析与实战
设计模式 ~ 深入理解建造者模式与实战
设计模式 ~ 工厂模式剖析与实战
设计模式 ~ 适配器模式分析与实战
设计模式 ~ 装饰模式探究
设计模式 ~ 深入理解代理模式
设计模式 ~ 小结

本文主要内容:

  • 代理模式概述
  • 静态代理
  • 动态代理
  • JDK 动态代理原理剖析
  • 代理模式 VS 装饰模式

代理模式概述

代理模式是使用非常广泛的一种设计模式,是一项基本的设计技巧,其他的模式诸如状态模式、策略模式、访问者模式本质上也采用了代理模式。

代理模式定义:为其他对象提供一种代理以控制对该对象的访问

生活中的代理也有很多,比如小商贩进货一般是向代理商拿货,而不是跑到生产厂家那里拿货;还有公司注册,有的自己懒得跑,交给专门中介公司处理,这些都是典型的代理模式的场景。

代理模式主要由以下 3 个角色组成:

  • 抽象主题(Subject)角色

    该角色是真是主题和代理主题的共同接口,以便在任何使用真实主题的地方都可以使用代理主题

  • 代理主题(Proxy Subject)角色

    也叫委托类、代理类,该角色负责控制对真实主题的引用,负责在需要的时候创建或删除真实主题对象,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

  • 真实主题(Real Subject)角色

    该角色也叫做被委托、被代理角色,是业务逻辑的具体执行者

代理模式的类图如下所示:

设计模式 ~ 深入理解代理模式_第1张图片

从上面的类图中我们可以发现代理角色真是主题角色 都实现了相同的接口也就是抽象主题角色,也就是真实主题出现的地方都可以使用代理角色

代理模式的实现方法主要有两种:静态代理和动态代理。

静态代理

根据上面的类图和代理模式角色的介绍,我们来实现一个注册公司的逻辑:有个人想要注册公司,但是自己不愿意跑工商局,然后将这个工作交给了中介公司处理:

注册公司的业务进行抽象(抽象主题)

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 动态代理原理剖析

上面介绍完了动态代理,接下来我们来看下 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 也就是 WeakCacheget 方法:

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 就是 FactoryFactory 实际上是 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 来实现,它是基于 继承 的,具体的相关资料读者可以查阅相关资料了解。

代理模式 VS 装饰模式

代理模式代理角色真实主题角色 接口保持一致; 装饰模式具体装饰角色具体构件 接口也是保持一致的。这两种模式也很容易混淆。

装饰模式 是用于为所装饰的对象增强功能,而代理模式是对对象的使用施加控制,并不提供对象本身的增强。看了很多模式相关的书籍,都是这么来区分代理模式和装饰模式的,但是读者还是不能很好的来区分它们之间的区别。比如说,我们常用代理模式来统计方法的执行效率或者方法的打点统计,那你说这是功能增强还是控制对象的访问呢?这里并没有控制对象的访问,对象的方法该怎么执行还是怎么执行,这好像是功能的增强啊,那这就是装饰模式吗?所以通过对象的功能增强还是对象的访问控制来区分并不好区分,而且代理模式用的时候也有可能不是来控制对象的访问的。

装饰模式中,会将具体构件通过构造方法传递给装饰器的,而代理模式中,可以将真实主题在代理类中创建,可以不用通过构造方法传递进来。如果装饰模式中如果具体构件在装饰器内部创建的话,装饰器的功能就大大减弱了,一般也不会这么做。所以说装饰模式更像是更加严苛的代理模式。除此之外,其他地方基本上是一样的。

Reference

  • 《Java与模式》
  • 《Java设计模式及实践》
  • 《Java设计模式深入研究》

如果你觉得本文帮助到你,给我个关注和赞呗!

另外本文涉及到的代码都在我的 AndroidAll GitHub 仓库中。该仓库除了 设计模式,还有 Android 程序员需要掌握的技术栈,如:程序架构、设计模式、性能优化、数据结构算法、Kotlin、Flutter、NDK,以及常用开源框架 Router、RxJava、Glide、LeakCanary、Dagger2、Retrofit、OkHttp、ButterKnife、Router 的原理分析 等,持续更新,欢迎 star。

你可能感兴趣的:(Android,设计模式)