什么是代理模式
代理模式(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
)。
? 这个部分的源码解析也可以看:www.cnblogs.com/liuyun1995/…
我们看看具体的应用:
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,甚至还可以用到像动态字节码生成这样的黑魔法去实现代理。只是需要对于各种代理的实现了然于心才能在实现过程中应用得当。