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

什么是代理模式

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

image.png

代理模式中通常有 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,甚至还可以用到像动态字节码生成这样的黑魔法去实现代理。只是需要对于各种代理的实现了然于心才能在实现过程中应用得当。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享