Java 代理模式详解

107 阅读7分钟

代理模式是一种结构型设计模式,用于在对象的访问或控制之前和之后添加额外的行为。在 Java 中,代理模式被广泛应用,以实现横切关注点(cross-cutting concerns)的分离、延迟加载、权限控制等功能。本文将详细介绍 Java 中的代理模式,包括静态代理和动态代理两种形式。

1. 代理模式概述

代理模式允许一个对象(代理对象)控制另一个对象(真实对象)的访问。代理对象通常充当客户端和真实对象之间的中介,可以在调用真实对象之前或之后执行一些额外的操作。代理模式的主要目的是提供对真实对象的间接访问,以便在不改变其结构的情况下,对其进行控制和增强。

2. 静态代理

静态代理是在编译时就已经创建代理类的代理形式。为了实现静态代理,我们需要创建一个代理类,该类与真实对象实现相同的接口,并在方法中调用真实对象的相应方法。

2.1 实现步骤

2.1.1 定义接口

public interface Subject {
    void request();
}

2.1.2 创建真正实现业务逻辑的类

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

2.1.3 创建代理类

public class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        // 在调用真实对象之前执行额外的操作
        System.out.println("Proxy: Pre-processing request.");

        // 调用真实对象的方法
        realSubject.request();

        // 在调用真实对象之后执行额外的操作
        System.out.println("Proxy: Post-processing request.");
    }
}

2.1.4 使用代理类

public class Client {
    public static void main(String[] args) {
        // 创建真实对象
        RealSubject realSubject = new RealSubject();

        // 创建代理对象,传入真实对象
        Proxy proxy = new Proxy(realSubject);

        // 通过代理对象调用方法
        proxy.request();
    }
}

输出结果

Proxy: Pre-processing request.
RealSubject: Handling request.
Proxy: Post-processing request.

2.2 静态代理的优缺点

2.2.1 优点

  • 结构清晰:代理模式通过接口的方式进行设计,使得系统结构更加清晰。
  • 易于扩展:可以通过增加新的代理类和真实对象类来扩展系统功能,无需修改已有代码。

2.2.2 缺点

  • 静态:在编译时已经确定代理类,不够灵活。如果要代理的类较多,可能需要创建大量的代理类。
  • 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护

3. 动态代理

3.1 JDK动态代理

动态代理是 Java 中一种非常强大且灵活的特性,它允许在运行时创建代理类和对象,而无需在编译时知道代理类的具体类型。JDK 动态代理是 Java 提供的一种基于接口的动态代理机制,它主要利用了 java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口。接下来我们将深入探讨 JDK 动态代理的实现原理和用法。

3.1.1 实现步骤

3.1.1.1 定义接口

public interface Subject {
    void doSomething();
}

3.1.1.2 创建真正实现业务逻辑的类

public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("RealSubject is doing something.");
    }
}

3.1.1.3 实现 InvocationHandler 接口

public class DynamicProxyHandler implements InvocationHandler {
    private Object realSubject;

    public DynamicProxyHandler(Object realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method execution.");

        // 调用被代理对象的方法
        Object result = method.invoke(realSubject, args);

        System.out.println("After method execution.");

        return result;
    }
}

3.1.1.4 创建代理对象

通过 Proxy.newProxyInstance 方法创建代理对象。

public class Client {
    public static void main(String[] args) {
        // 创建被代理对象
        RealSubject realSubject = new RealSubject();

        // 创建代理对象
        // 1.ClassLoader loader: 用于定义代理类的类加载器,通常使用被代理类的类加载器。  
        // 2.Class<?>[] interfaces: 代理类需要实现的接口数组,被代理类的所有接口都应该在这里列出。  
        // 3.InvocationHandler h: 一个 `InvocationHandler` 接口的实现,用于在代理对象的方法被调用时指定方法的行为
        Subject proxy = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                new DynamicProxyHandler(realSubject));
        // 调用代理对象的方法
        proxy.doSomething();
    }
}

3.1.2 JDK 动态代理的原理

3.1.2.1 生成代理类的字节码、加载代理类

Proxy.newProxyInstance 方法通过传入的 ClassLoaderinterfaces 参数,使用 ProxyGenerator 生成代理类的字节码。通过 ClassLoader 加载生成的代理类字节码,得到代理类的 Class 对象。

public class Proxy implements java.io.Serializable {
     //  省略
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>

    {
    //  省略
    /*
     * 生成代理类的字节码.
     * proxyName 是代理类的全限定名,interfaces 是接口数组,accessFlags 是代理类的访问标志。
     */
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces, accessFlags);
    //  省略
    // 加载代理类字节码,得到代理类的 Class 对象 
    return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
    }
    //  省略
}

生成的代理类字节码结构大致如下:


import com.example.demo.jdkdynamicproxy.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class RealSubjectProxy extends Proxy implements Subject {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public RealSubjectProxy(InvocationHandler var1) throws  {
        super(var1);
    }

    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 void doSomething() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.example.demo.jdkdynamicproxy.Subject").getMethod("doSomething");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

3.1.2.2 实例化代理对象,关联 InvocationHandler

通过反射机制,调用代理类的构造方法实例化代理对象。将传入的 InvocationHandler 对象与代理对象关联起来。

public class Proxy implements java.io.Serializable {
     //  省略
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            throws IllegalArgumentException {
        Objects.requireNonNull(h);

        // 得到代理类的 Class 对象
        Class<?> cl = getProxyClass0(loader, interfaces);

        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            // 获取代理类的构造方法
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 实例化代理对象 传入InvocationHandler
            return cons.newInstance(new Object[]{h});
        } catch (Exception e) {
            //  省略
        }
    }
    //  省略
}

3.1.2.3 代理对象方法调用

当调用代理对象的方法时,实际上是调用 InvocationHandlerinvoke 方法,在该方法中定义了代理对象方法的具体行为。

3.1.3 JDK 动态代理的特点

  1. 基于接口: JDK 动态代理要求被代理的类必须实现一个接口,因为生成的代理类需要实现相同的接口。
  2. 运行时生成: 代理类是在运行时生成的,而不是在编译时。
  3. 无需手动编写代理类: 与静态代理不同,JDK 动态代理无需手动编写代理类,减少了开发工作量。
  4. 灵活性: 由于代理类是在运行时生成的,因此具有更大的灵活性,可以代理多个接口。

3.2 CGLIB 动态代理

3.3.1 CGLIB 动态代理的使用

3.3.1.1 添加依赖

首先,需要在项目中添加 CGLIB 的依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version> <!-- 请根据实际情况选择版本 -->
</dependency>

3.3.1.2 创建被代理的类

public class RealSubject {
    public void doSomething() {
        System.out.println("RealSubject is doing something.");
    }
}

3.3.1.3 创建 CGLIB 代理类

public class CglibProxyInterceptor implements MethodInterceptor {
    private Object target;

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

    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method execution.");

        // 调用被代理对象的方法
        Object result = proxy.invoke(target, args);

        System.out.println("After method execution.");

        return result;
    }

}

3.3.1.4 测试

public class CglibTest {

    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        CglibProxyInterceptor cglibProxyInterceptor = new CglibProxyInterceptor(realSubject);

        // 获取 CGLIB 代理对象
        RealSubject proxy = (RealSubject) cglibProxyInterceptor.getProxy();

        // 调用代理对象的方法
        proxy.doSomething();
    }
}

执行结果

Before method execution.
RealSubject is doing something.
After method execution.

3.3.2 CGLIB 动态代理的特点

  1. 基于类代理: CGLIB 动态代理是基于类的代理,它创建一个被代理类的子类,通过重写被代理类的方法来实现代理。
  2. 无需实现接口: 与 JDK 动态代理不同,CGLIB 不要求被代理类实现接口,它可以直接代理类的所有方法。
  3. 性能相对较高: CGLIB 是通过直接操作字节码生成代理类,因此在性能上相对较高,特别是对于方法调用的性能。
  4. 不支持 final 方法: 由于 CGLIB 是通过继承生成代理类,因此不能代理被 final 修饰的方法。