代理模式在 Java 中的实现与原理分析

什么是代理模式

代理模式(Proxy Pattern)是常用的设计模式之一,通过代理模式,我们可以在不侵入原对象的前提下,对对象访问的输入输出进行干预,以实现某些增强功能。比如请求拦截器、性能统计,或者对 AOP 思想的实践都是对代理模式的应用。并且代理模式也是面向对象设计原则中 开闭原则(OCP)的实践,在不修改原实体的情况下,通过代理的方式实现能力的扩展。

代理模式中通常有 3 个角色:

Subject:一组可以被代理的行为的集合,通常是一个接口ProxySubject:代理对象,消费端通过它来访问实际的对象RealSubject:实际被代理的对象

如何实现代理模式

在 Java 中实现代理模式可以有 3 中方式:静态代理、动态代理、cglib代理。

静态代理

静态代理是指在程序运行前,代理类已经存在于编译结果之中,要实现静态代理,首先声明代表要代理行为的集合的接口 Subject:

package me.leozdgao; public interface Subject { void action(String name); }

接下来定义一个将要被代理对象的类,它需要实现刚才的 Subject 接口:

package me.leozdgao; public class RealSubject implements Subject { @Override public void action(String name) { System.out.println("Get name: " + name); } }

代理基于被代理的对象构造,代理类的实现如下,代理类的方法实现就是调用对应的被代理对象的方法,并且调用前后可以进行额外逻辑的实现:

package me.leozdgao; public class ProxySubject implements Subject { private Subject target; ProxySubject(Subject realOne) { this.target = realOne; } @Override public void action(String name) { System.out.println("Before action"); this.target.action(name); System.out.println("After action"); } }

最后是静态代理具体的应用:

package me.leozdgao; public class Main { public static void main(String[] args) { RealSubject real = new RealSubject(); ProxySubject proxy = new ProxySubject(real); proxy.action("demo"); } }

静态代理实现上比较清晰简单,在简单场景下够用,但也存在几个明显的问题:

代理类和被代理类必须继承同一个接口,接口的变更需要同时修改代理类和被代理类代理行为的抽象不够通用,对于每次的代理行为,都需要针对性的声明一个接口

动态代理

接下来看看动态代理的方式,动态代理是基于 JDK 的反射 API 实现的:

package me.leozdgao; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyFactory { private final Object target; public ProxyFactory(Object target) { this.target = target; } public Object getProxyInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new ProxyInvocationHandler(target)); } static class ProxyInvocationHandler implements InvocationHandler { private final Object target; public ProxyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before action"); Object result = method.invoke(target, args); System.out.println("After action"); return result; } } }

与前者静态代理不同,动态代理可以实现一个较通用的代理工厂,通过 JDK 的 Proxy API Proxy.newProxyInstance 创建了一个代理对象实例,并通过自定义一个实现 InvocationHandler 接口的类来实现代理的行为。

Proxy API 背后的原理是基于我们传入 interfaces,动态创建了一个继承 Proxy 类并实现了我们提供的 interfaces 的类的字节码,并通过我们传入的 classLoader 将这个类加载进来,具体可以参考 JDK 的源码(生成类的字节码的核心代码是 sun.misc 包下的 ProxyGenerator)。

这个部分的源码解析也可以看:https://www.cnblogs.com/liuyun1995/p/8144706.html

我们看看具体的应用:

package me.leozdgao; public class Main { public static void main(String[] args) { RealSubject subject = new RealSubject(); ProxyFactory factory = new ProxyFactory(subject); Subject proxy = (Subject) factory.getProxyInstance(); proxy.action("myName"); } }

在这里我们代理的是 Object 类型的对象,也可以根据需要,针对不同类型的对象定义不同的代理工厂。在应用的过程中,我们可以看出动态代理相较于静态代理,增加了额外的好处:

不再受限于必须为每个需要被代理的接口实现代理类

但从实现过程中也会发现,被代理的类必须实现接口,才能基于 JDK Proxy API 实现动态代理,当我们需要对二方或者三方提供的类实现代理的话,这将是个不小的限制。

cglib代理

cglib 是一个比较早期的社区包,基于 asm 包在运行时生成字节码,和上面谈到的动态代理背后的思路是比较类似的。有差异的地方在于,它动态生成的类是继承与被代理类的,这样就可以解决因为没有实现接口而无法使用动态代理的问题,当然因为它背后是继承,那么同时也带来了几个问题:

被修饰为 final 的类无法被代理被修饰为 final 的类方法代理会失效

我们来看一个具体的例子,首先是一个没有实现任何接口的类:

package me.leozdgao; public class StandaloneSubject { public String echo(String name) { return name; } public void doAction(String label) { System.out.println("do something with label " + label); } }

接下来我们基于 cglib 来实现一个代理工厂:

package me.leozdgao; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class ProxyFactoryWithCglib implements MethodInterceptor { private Object target; public ProxyFactoryWithCglib(Object target) { this.target = target; } public Object getProxyInstance() { Enhancer en = new Enhancer(); en.setSuperclass(target.getClass()); en.setCallback(this); return en.create(); } @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("Before action"); Object result = method.invoke(target, args); System.out.println("after action"); return result; } }

然后是代理的具体应用:

package me.leozdgao; public class Main { public static void main(String[] args) { StandaloneSubject subject = new StandaloneSubject(); ProxyFactoryWithCglib factory = new ProxyFactoryWithCglib(subject); StandaloneSubject proxy = (StandaloneSubject) factory.getProxyInstance(); proxy.doAction("label"); } }

但是 cglib 由于历史悠久,迭代逐渐跟不上 Java 社区的发展,在它 github 的项目 README 中也写到 JDK 17+ 的支持比较不完善,建议我们尝试其他字节码动态生成方案,比如 ByteBuddy(不过由于背后思想类似,本文就不再展开演示)。

不同代理实现的应用

似乎有多种实现代理模式的方案,每个方案似乎又各有优劣,我们综合起来总结比较一下:

静态代理的应用比较有局限,由于必须特别针对被代理类的接口实现一个代理类,在通用性上有较大的欠缺,难以将代理的实现单独提炼出来,应用的比较少。

动态代理和 cglib 代理背后都是在运行动态生成,可实现的代理方案都具备一定的通用性,只是一个是 JDK 的标准 API,一个需要额外引入一个三方依赖。但二者基本都有硬伤,比如基于 JDK Proxy API 的动态代理必须要求被代理类实现接口,代理的是这些接口的方法,而 cglib 代理是基于类继承,虽然没有前者的局限,但对于 final 类或者修饰为 final 的类方法束手无策。大部分场景下二者都没有绝对的优势,需要基于他们背后的实现原理自行判断选型。

Spring AOP 代理的应用

Spring 的 AOP 实现就是基于代理,那么背后又是如何实现的呢?其实也是用到的文中的方法,并且通过在运行时动态决策的,可以看到 DefaultAopProxyFactory 的源码实现:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { // ... @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (!NativeDetector.inNativeImage() && (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } } // ... }

可以发现,在 Spring 中可以通过 optimize 或者 proxyTargetClass 来指定使用 cglib 代理,并且当被代理类没有实现接口的时候也会使用 cglib 代理,其他情况下则会使用基于 JDK Proxy API 的动态代理。

总结

Java 作为一门强类型的语言,通过强类型的约束提供了开发的效率和稳定性,但像需要应用一个较通用的代理模式,相对动态类型语言就会显的有一些吃力。但 Java 生态肯定还是非常完善成熟的,JDK 提供基于标准的代理 API,甚至还可以用到像动态字节码生成这样的黑魔法去实现代理。只是需要对于各种代理的实现了然于心才能在实现过程中应用得当。