您现在的位置是:首页 >技术杂谈 >【设计模式】代理模式网站首页技术杂谈

【设计模式】代理模式

千筠Wyman 2024-08-23 00:01:03
简介【设计模式】代理模式

Java 的代理模式是一种设计模式,它可以让一个对象(代理对象)代替另一个对象(目标对象)去执行一些操作,并且可以在执行前后添加一些额外的功能。代理模式可以实现对目标对象的功能扩展和保护。

Java 的代理模式有两种实现方式:静态代理和动态代理。静态代理是在编译时就生成了代理类的字节码文件,而动态代理是在运行时动态生成代理类并加载到 JVM 中。

静态代理

静态代理的实现步骤如下:

  • 定义一个接口及其实现类;
  • 创建一个代理类同样实现这个接口;
  • 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法;
  • 通过代理类来访问目标对象的方法,并且可以在方法执行前后添加自定义的操作。

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活。需要对每个目标类都单独写一个代理类,而且接口一旦新增加方法,目标对象和代理对象都要进行修改。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。从 JVM 层面来说,静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

动态代理

动态代理的实现方式有多种,比如 JDK 动态代理、CGLIB 动态代理等。JDK 动态代理是利用 Java 内部的反射机制生成一个实现了目标接口的匿名类,然后调用目标方法;CGLIB 动态代理是利用 ASM 开源包,对目标类的字节码文件进行修改,生成子类来处理目标方法。CGLIB 动态代理这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

JDK 动态代理

JDK动态代理是面向接口的,必须要实现了接口的业务类才生成代理对象。

在运行期动态创建一个interface实例的方法如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;

  2. 通过 Proxy.newProxyInstance() 创建实例,它需要3个参数:

    • 使用的 ClassLoader,通常就是接口实现类的 ClassLoader
    • 需要实现的接口数组,至少需要传入一个接口进去;
    • 用来处理接口方法调用的 InvocationHandler 实例。
  3. 将返回的 Object 强制转型为接口。

示例实现代码:

public interface RequestInterface {
    void request();
}

public class RequestInstance implements RequestInterface {
    @Override
    public void request() {
        System.out.println("print request.");
    }
}

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JdkProxyHandler implements InvocationHandler {

    private Object instance;

    public JdkProxyHandler(Object instance) {
        this.instance = instance;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before instance execution");
        System.out.println("Method:" + method);
        method.invoke(instance, args);
        System.out.println("after instance execution");
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        RequestInstance instance = new RequestInstance();
        JdkProxyHandler proxyHandler = new JdkProxyHandler(instance);
        ClassLoader classLoader = instance.getClass().getClassLoader();
        RequestInterface requestInst = (RequestInterface) Proxy.newProxyInstance(
                classLoader, new Class[]{RequestInterface.class}, proxyHandler);
        requestInst.request();
    }
}

运行上述代码之后,控制台打印出:

before instance execution
Method:public abstract void jdk.instance.RequestInterface.request()
print request.
after instance execution

可以看出,通过代理的方式,已经成功增强了 RequestInstancerequest() 方法。

如果想通过 JDK 动态代理实现一个通用的代理,那么可以在代理处理器中实现一个创建代理的静态方法,通过传入不同的实现类来实现对同一接口、不同实现的通用代理。假设实现一个统计接口耗时的代理:

public class JdkProxyRuntimeHandler implements InvocationHandler {

    private Object target;

    public JdkProxyRuntimeHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long starTime = System.nanoTime();
        Object result = method.invoke(this.target, args);//@1
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + " 方法耗时(纳秒): " + (endTime - starTime));
        return result;
    }

    /**
     * 用来创建targetInterface接口的代理对象
     *
     * @param target          被代理的对象
     * @param targetInterface 被代理的接口
     * @param <T> 实现类类名
     * @return 代理对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(Object target, Class<T> targetInterface) {
        if (!targetInterface.isInterface()) {
            throw new IllegalStateException("targetInterface 不属于接口类型!");
        } else if (!targetInterface.isAssignableFrom(target.getClass())) {
            throw new IllegalStateException("target 不是 targetInterface 接口的实现类!");
        }

        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new JdkProxyRuntimeHandler(target));
    }
}

public class Main {
    public static void main(String[] args) {
        RequestInterface proxyInstance = JdkProxyRuntimeHandler.createProxy(new RequestInstance(), RequestInterface.class);
        proxyInstance.request();
    }
}

上面当我们创建一个新的接口的时候,不需要再去新建一个代理类了,只需要使用 JdkProxyRuntimeHandler.createProxy 创建一个新的代理对象就可以了,方便了很多。

使用 JDK 动态代理需要注意几点:

  • JDK 中的 Proxy 只能为接口生成代理类,如果想给某个类创建代理类,那么需要用到 CGLIB 了
  • 当调用通过 Proxy 创建代理对象的任意方法时候,会被 InvocationHandler 接口中的 invoke 方法进行处理

CGLIB 动态代理

JDK 的动态代理只能为接口创建代理,在应用上具有局限性。在实际的场景中,我们的类不一定有接口,如果我们想为普通的类也实现代理功能,我们就需要用到CGLIB 来实现了。

CGLIB 是一个强大、高性能的字节码生成库,底层使用了 ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。它用于在运行时扩展 Java 类和实现接口,本质上它是通过动态生成一个子类去覆盖所要代理的类。Enhancer 可能是 CGLIB 中最常用的一个类,和 JDK 中的 Proxy 不同的是,Enhancer既能够代理一般的类,也能够代理接口。Enhancer 创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的 toStringhashCode 方法)。Enhancer 不能够拦截final方法,例如 Object.getClass() 方法,这是由 Java final 方法语义决定的。基于同样的道理,Enhancer 也不能对 final 类进行代理操作。

由于 Spring 已将第三方 CGLIB Jar 包中所有的类集成到 Spring 自己的 Jar 包中,因此可直接使用接口 MethodInterceptor 来实现代理。

CGLIB动态代理的基本实现代码:

public class RawRequestInstance {
    public String request() {
        System.out.println("print request.");
        return "print raw request.";
    }
}

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

public class CglibProxy {

    private Object target;

    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        // 声明生成的代理类是某一个父类的子类
        enhancer.setSuperclass(this.target.getClass());
        // 指定回调方法,即代理中需要加强的内容
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            System.out.println("before instance execution");
            System.out.println("Method:" + method);
            Object result = methodProxy.invokeSuper(o, objects);
            System.out.println("after instance execution");
            return result;
        });
        return enhancer.create();
    }
}

public class Main {
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        RawRequestInstance proxyInst = (RawRequestInstance) proxy.getInstance(new RawRequestInstance());
        System.out.println(proxyInst.request());
    }
}

运行上述代码之后,控制台打印出:

before instance execution
Method:public void instance.RawRequestInstance.request()
print request.
after instance execution
print raw request.

实际上,CGLIB 代理类在方法被调用时,里面的所有的方法都是会被拦截处理的,下面我们修改一下实现类 RequestInstance 的成员方法,然后再执行一次以上的代码:

public class RawRequestInstance {
    public String request() {
        System.out.println("print request.");
        return "print raw request." + request2();
    }

    public String request2() {
        System.out.println("print request2.");
        return "print raw request 2.";
    }
}

运行上述代码之后,控制台打印出:

before instance execution
Method:public java.lang.String instance.RawRequestInstance.request()
print request.
before instance execution
Method:public java.lang.String instance.RawRequestInstance.request2()
print request2.
after instance execution
after instance execution
print raw request.print raw request 2.

可见,即便 request2() 是在 request1() 中被调用,但它仍然被代理拦截进行处理。实际上这就是 Spring 中的 @Configuration 注解的实现方式,使用 CGLIB 代理拦截 @Bean 注解的方法,从而确保注入的被依赖 Bean 都是同一个。

同样地,我们实现一个通用的、用于统计接口耗时的代理:

public class CglibProxyRuntime implements MethodInterceptor {

    private Object target;

    public CglibProxyRuntime(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long starTime = System.nanoTime();
        Object result = method.invoke(target, objects);
        long endTime = System.nanoTime();
        System.out.println(method + " 方法耗时(纳秒): " + (endTime - starTime));
        return result;
    }

    /**
     * 创建任意类的代理对象
     *
     * @param target 被代理的对象
     * @param <T> 实现类类名
     * @return 代理对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target) {
        CglibProxyRuntime costTimeProxy = new CglibProxyRuntime(target);
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(costTimeProxy);
        enhancer.setSuperclass(target.getClass());
        return (T) enhancer.create();
    }
}

public class Main {
    public static void main(String[] args) {
        RawRequestInstance proxy = CglibProxyRuntime.createProxy(new RawRequestInstance());
        System.out.println(proxy.request());
    }
}

输出结果为:

print request.
print request2.
public java.lang.String instance.RawRequestInstance.request() 方法耗时(纳秒): 403800
print raw request.print raw request 2.

从上面的示例代码可知,CGLIB 代理拦截方法后的处理逻辑,主要是通过自定义一个 MethodInterceptor 接口并重写其方法,将该接口赋值给 enhancer 作为 callback 回调方法。这里的 MethodInterceptor 其实也是继承了 Callback 接口,因此我们还可以定义其他继承了 Callback 的接口,从而增强代理类的功能。

FixedValue 返回固定值

enhancer.setCallback(new FixedValue() {
    @Override
    public Object loadObject() throws Exception {
        return "**** Fixed Value ****";
    }
});

在上面的代码中直接修改 setCallback() 方法的赋值,最终输出结果为:

**** Fixed Value ****

NoOp.INSTANCE 拦截无操作

直接上代码:

enhancer.setCallback(NoOp.INSTANCE);

最终输出结果为:

print request.
print request2.
print raw request.print raw request 2.

CallbackFilter 不同方法实现不同拦截策略

定义一个新的实现类 MultiMethodInstance

public class MultiMethodInstance {

    public String runtime() {
        System.out.println("print runtime.");
        return "print runtime.";
    }

    public String fixedValue() {
        System.out.println("fixed value");
        return "fixed value";
    }

    public String doNothing() {
        System.out.println("----------- do nothing -----------");
        return "----------- do nothing -----------";
    }
}

然后定义一个新的代理拦截类:

import org.springframework.cglib.proxy.*;
import java.lang.reflect.Method;

public class CglibProxyFilter {

    private Object target;

    public Object getInstance(Object target) {
        this.target = target;

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());

        //创建多个Callback
        Callback[] callbacks = {
                // 拦截所有方法名含有 run 的方法
                new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        long starTime = System.nanoTime();
                        Object result = methodProxy.invokeSuper(o, objects);
                        long endTime = System.nanoTime();
                        System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
                        return result;
                    }
                },
                // 拦截所有方法名含有 fixed 的方法,返回固定值的
                new FixedValue() {
                    @Override
                    public Object loadObject() throws Exception {
                        return "**** Fixed Value ****";
                    }
                },
                // 拦截后无操作
                NoOp.INSTANCE
        };
        enhancer.setCallbacks(callbacks);

        // 设置过滤器 CallbackFilter,判断调用方法时使用哪个 Callback 来处理,返回的是 callbacks 数组的下标
        enhancer.setCallbackFilter(new CallbackFilter() {
            @Override
            public int accept(Method method) {
                String methodName = method.getName();
                return methodName.contains("run") ? 0
                        : (methodName.contains("fixed") ? 1 : 2);
            }
        });

        return enhancer.create();
    }
}

通过定义多个 Callback 接口的实现,以及提供判断每个方法调用时所选用的回调策略(拦截处理),来完成对同一个实现类中不同方法进行不同的代理拦截策略。具体执行哪个 Callback,会通过 CallbackFilter 中的 accept 方法来判断,这个方法返回 callbacks 数组的索引。上述代码的输出结果如下:

print runtime.
public java.lang.String instance.MultiMethodInstance.runtime(),耗时(纳秒):29824600
print runtime.
**** Fixed Value ****
----------- do nothing -----------
----------- do nothing -----------

多策略拦截代理还能进一步优化,使用 CallbackHelper 替代 CallbackFilter 能使代码结构更加清晰,扩展性更强。具体实现代码如下:

import org.springframework.cglib.proxy.*;
import java.lang.reflect.Method;

public class CglibProxyCallbackHelper {

    private Object target;

    public Object getInstance(Object target) {
        this.target = target;

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());

        // 定义所有可用的 Callback 接口方法
        Callback costTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> {
            long starTime = System.nanoTime();
            Object result = methodProxy.invokeSuper(o, objects);
            long endTime = System.nanoTime();
            System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
            return result;
        };
        Callback fixedValueCallback = (FixedValue) () -> "**** Fixed Value ****";
        Callback noOpCallback = NoOp.INSTANCE;

        // 定义拦截策略的判断逻辑
        CallbackHelper callbackHelper = new CallbackHelper(target.getClass(), null) {
            @Override
            protected Object getCallback(Method method) {
                String methodName = method.getName();
                return methodName.contains("run") ? costTimeCallback
                        : (methodName.contains("fixed") ? fixedValueCallback : noOpCallback);
            }
        };

        enhancer.setCallbacks(callbackHelper.getCallbacks());
        enhancer.setCallbackFilter(callbackHelper);

        return enhancer.create();
    }
}

优化后的输出结果不变,这里就不再展示了。

小结

Java 标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例。动态代理是通过 Proxy 创建代理对象,然后将接口方法“代理”给 InvocationHandler 完成的。动态代理实际上是 JVM 在运行期动态创建 class 字节码并加载的过程

JDK 动态代理和 CGLIB 动态代理的区别在于:

  • JDK 动态代理是面向接口的,必须要实现了接口的业务类才生成代理对象;而 CGLIB 动态代理则是通过生成业务类的子类作为代理类,无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。
  • Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
  • Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB 使用 ASM 框架直接对字节码进行操作,在类的执行过程中比较高效。

在 SpringBoot 中,有很多地方应用了代理模式,比如 AOP、事务管理、缓存等。例如,当我们使用 @Transactional 注解来开启事务时,SpringBoot 会根据目标类是否实现了接口来选择使用 JDK 动态代理还是 CGLIB 动态代理来创建一个带有事务功能的代理对象。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。