Java中的静态代理与动态代理

109 阅读16分钟

静态代理与动态代理

静态代理

Java中的静态代理是一种设计模式,它允许我们通过一个代理类来控制对另一个类的访问。这个代理类(代理对象)和目标类(被代理对象)实现相同的接口,代理类内部持有目标类的一个实例,并可以在调用目标类的方法前后执行一些附加操作,比如权限检查、日志记录、事务处理等。

静态代理的"静态"二字意味着代理类是手动创建或固定的,它在编译时已经确定,与之相对的是动态代理,后者在运行时动态创建代理类和对象。

静态代理的组成部分

  1. 接口(Interface) :定义了代理类和目标类需要实现的方法。
  2. 目标类(Target Class) :实现接口的被代理类,包含业务逻辑。
  3. 代理类(Proxy Class) :也实现了接口,并持有目标类的引用,在调用目标类的方法前后可以执行额外的操作。

静态代理的实现步骤

下面是一个简单的静态代理实现的例子:

假设有一个简单的接口Subject和一个实现了这个接口的实际类RealSubject,我们想通过一个代理类ProxySubject来访问RealSubject的方法。 定义接口(Massage) :

public interface Massage {
    void request();
}

实现接口的目标类(Agent)

public class Agent implements Massage {
    @Override
    public void request() {
        System.out.println("Executing request in Agent");
    }
}

代理类(ProxySubject)

public class ProxySubject implements Massage {
    private Agent realSubject; // 持有对真实主题的引用

    public ProxySubject(Subject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        preRequest();
        realSubject.request(); // 调用真实主题的方法
        postRequest();
    }

    private void preRequest() {
        System.out.println("Pre Request in ProxySubject");
    }

    private void postRequest() {
        System.out.println("Post Request in ProxySubject");
    }
}

使用代理

public class Client {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxySubject proxy = new ProxySubject(realSubject);
        proxy.request(); // 使用代理访问
    }
}

静态代理的优缺点

优点:

  • 简单易懂,易于实现。
  • 在不修改目标对象的前提下扩展目标对象的功能,因为我们可以直接通过修改Proxy类来实现功能的拓展。

缺点:

  • 每个代理类只能为一个接口服务,功能一致但接口不同的类需要额外的代理类。
  • 代理和实际对象之间的增加了耦合度。
  • 系统中类的数量可能会大量增加,增加了系统的复杂度。

静态代理虽然简单,但在实际使用中由于其灵活性不足,动态代理(如Java的动态代理、CGLIB等)通常是更受欢迎的选择。

动态代理

Java动态代理主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。Proxy类用于在运行时动态创建代理对象,而InvocationHandler则用于定义代理对象的调用处理程序。

当通过Proxy创建代理对象并对其进行方法调用时,实际上是调用了InvocationHandlerinvoke方法。在这个方法内,可以定义在调用实际方法前后要执行的逻辑,比如日志记录、权限检查、事务处理等。

对比静态代理的区别, 静态代理中使用对象对被代理方法进行调用, 动态代理统一由 InvocationHandler 进行方法反射调用

定义一个接口

public interface Subject {
    void doAction();
}

实现接口的目标类

public class RealSubject implements Subject {
    @Override
    public void doAction() {
        System.out.println("Executing action in RealSubject");
    }
}

创建调用处理程序

public class DynamicProxyHandler implements InvocationHandler {
    private Object subject; // 实际对象的引用

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = method.invoke(subject, args); // 调用实际对象的方法
        System.out.println("After method: " + method.getName());
        return result;
    }
}

public Object invoke(Object o, Method method, Object[] objects){}

这个方法中。 在Java动态代理机制中,InvocationHandler接口的invoke方法是核心方法,它用于处理代理实例上的方法调用。invoke方法有三个参数,分别是proxymethodargs,它们的作用如下:

1. Object proxy

proxy参数代表的是代理对象本身,即调用方法的代理实例。通过这个对象,可以执行代理对象上的其他方法(尽管直接这么做可能会导致无限递归调用,特别是在调用像toStringhashCodeequals这样的方法时)。然而,在大多数情况下,不需要通过proxy对象直接操作,除非有特定的需求,比如计算代理对象的哈希码或比较代理对象。

  • 使用注意:直接在invoke方法内部调用proxy的方法很容易导致无限递归,因为这些调用会再次被路由到invoke方法,最终可能导致StackOverflowError

2. Method method

  • 类型java.lang.reflect.Methodmethod参数表示代理对象上被调用的方法。这个Method对象提供了关于方法本身的所有信息,包括方法名、返回类型、参数类型等。可以使用这个对象来决定如何处理当前的方法调用,比如只对特定的方法执行某些操作。
  • 使用方式:通过检查method的名称或其他属性来实现条件逻辑,或者直接使用method.invoke(...)来对特定目标对象调用原始方法。

3. Object[] args

args参数是一个对象数组,包含了代理方法调用时传递的所有实际参数。这些是调用者传给方法的参数,我们可以对这些参数进行操作,比如修改参数值,或者基于这些参数值进行特定的逻辑处理。

  • 使用方式args允许读取、修改或者分析调用方法时使用的参数。例如,可以记录方法调用的参数值,或者在调用原始方法之前修改这些参数。

使用Proxy类动态创建代理对象并使用代理

public class Client {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
                RealSubject.class.getClassLoader(),
                new Class[]{Subject.class},
                new DynamicProxyHandler(realSubject));

        proxySubject.doAction(); // 使用代理执行方法
    }
}

Proxy.newProxyInstance方法用于在运行时创建一个实现了Subject接口的代理对象。这个代理对象内部使用DynamicProxyHandler来处理所有的方法调用。

动态代理的优点

  • 灵活性:动态代理允许我们在运行时创建代理对象,提高了代码的灵活性和复用性。
  • 简化开发:通过动态代理可以轻松实现横切关注点(如日志、安全等)的统一处理,简化了开发。
  • 减少重复代码:避免了为每个需要代理的类手动编写代理类的需求,减少了重复代码。

如果有多个接口,例如还有Subject2, Subject3等,我们在使用这个代码的时候,还可以这样写:

Subject proxySubject = (Subject) Proxy.newProxyInstance(
                RealSubject.class.getClassLoader(),
                new Class[]{Subject.classSubject2.classSubject3.class},
                new DynamicProxyHandler(realSubject));

之后需要调整DynamicProxyHandler:

public class DynamicProxyHandler implements InvocationHandler {
    private Object subject1;
    private Object subject2;
    private Object subject3;

    public DynamicProxyHandler(Object subject1, Object subject2, Object subject3) {
        this.subject1 = subject1;
        this.subject2 = subject2;
        this.subject3 = subject3;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Method called: " + method.getName());
        // 根据method所属的接口类型,将调用分派到不同的实际对象
        if (Arrays.asList(subject1.getClass().getInterfaces()).contains(method.getDeclaringClass())) {
            return method.invoke(subject1, args);
        } else if (Arrays.asList(subject2.getClass().getInterfaces()).contains(method.getDeclaringClass())) {
            return method.invoke(subject2, args);
        } else if (Arrays.asList(subject3.getClass().getInterfaces()).contains(method.getDeclaringClass())) {
            return method.invoke(subject3, args);
        }
        throw new RuntimeException("Method not supported: " + method.getName());
    }
}

上边的方式中:

  1. subject1.getClass().getInterfaces() : 获取subject1对象的类实现的所有接口。这部分返回的是一个Class<?>[]数组,每个元素代表一个接口的Class对象。
  2. Arrays.asList(...) : 将上一步获取的接口数组转换成一个List<Class<?>>。这样做是为了利用List提供的方法,如contains,来检查列表中是否包含特定元素。
  3. method.getDeclaringClass() : 获取声明了当前正在被调用的method的类(或接口)。对于接口中定义的方法,这将返回该接口的Class对象。
  4. contains(...) : 检查从subject1的类实现的接口列表中是否包含method所声明的接口。如果包含,说明methodsubject1实现的接口之一的方法。

如果不想调整,也可以通过RealSubject同时实现subject2和subject3来实现同样的效果。

public class RealSubject implements Subject,Subject2,Subject3 {
    @Override
    public void doAction() {
        System.out.println("Executing action in RealSubject");
    }

    @Override
    public void doAction2() {   
    }

    @Override
    public void doAction3() {
    }
}

自带实现类的整体源码分析

// 1. 定义业务接口 Subject
public interface Subject {
    // 客户端想要调用的业务方法
    void doAction();
}

// 2. 真实的实现类 RealSubject
public class RealSubject implements Subject {
    @Override
    public void doAction() {
        // 真实业务逻辑在这里执行
        System.out.println("Executing action in RealSubject");
    }
}

// 3. 动态代理的处理器:拦截所有方法调用并委托给真实对象
public class DynamicProxyHandler implements InvocationHandler {
    // 持有真实对象引用,用于反射调用
    private Object subject;

    public DynamicProxyHandler(Object subject) {
        // 在创建代理时传入真实对象
        this.subject = subject;
    }

    /**
     * 任何通过代理对象调用的接口方法,都会路由到这里
     *
     * @param proxy  代理实例本身(通常不用直接调用它)
     * @param method 被调用的方法的反射对象
     * @param args   方法参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置处理:打印方法名
        System.out.println("Before method: " + method.getName());

        // 核心委托:反射调用真实对象的方法,执行实际业务
        Object result = method.invoke(subject, args);

        // 后置处理:打印方法名
        System.out.println("After method: " + method.getName());
        return result;
    }
}

// 4. 客户端使用动态代理
public class Client {
    public static void main(String[] args) {
        // 4.1 创建真实对象
        RealSubject realSubject = new RealSubject();

        // 4.2 生成代理实例
        //    - 使用 RealSubject 的 ClassLoader
        //    - 指定代理需要实现的接口 Subject
        //    - 传入上面定义的 DynamicProxyHandler
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
            RealSubject.class.getClassLoader(),
            new Class[]{Subject.class},
            new DynamicProxyHandler(realSubject)
        );

        // 4.3 调用代理,相当于:
        //     $Proxy0.doAction() → handler.invoke(...) → realSubject.doAction()
        proxySubject.doAction();
    }
}

// 5. 下面是 JVM 运行时生成的代理类 $Proxy0(简化自 FernFlower 反编译)
//    它 extends java.lang.reflect.Proxy 并 implements Subject
package jdk.proxy1;

public final class $Proxy0 extends Proxy implements Subject {
    // 缓存 Method 对象,指向 Object.hashCode / equals / toString / Subject.doAction
    private static final Method m0;  // Object.hashCode
    private static final Method m1;  // Object.equals(Object)
    private static final Method m2;  // Object.toString
    private static final Method m3;  // Subject.doAction()

    // 构造函数:把用户传入的 InvocationHandler 交给父类存储
    public $Proxy0(InvocationHandler h) {
        super(h);
    }

    // ---------- 以下每个方法都把调用转给 InvocationHandler ----------

    public final int hashCode() {
        try {
            // super.h 就是 DynamicProxyHandler 实例
            return (Integer) super.h.invoke(this, m0, null);
        } catch (Throwable t) {
            // 把受检异常包装成 RuntimeException
            throw new UndeclaredThrowableException(t);
        }
    }

    public final boolean equals(Object obj) {
        try {
            return (Boolean) super.h.invoke(this, m1, new Object[]{obj});
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    public final String toString() {
        try {
            return (String) super.h.invoke(this, m2, null);
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    // 这是代理了 Subject.doAction()
    public final void doAction() {
        try {
            // 调用 InvocationHandler.invoke(proxyInstance, Method m3, null)
            super.h.invoke(this, m3, null);
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    // 静态初始化:反射获取各方法到 Method 对象
    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            // 注意这里包名要和接口 Subject 的包保持一致
            m3 = Class.forName("Subject 所在的完整包名.Subject").getMethod("doAction");
        } catch (Exception e) {
            throw new RuntimeException("初始化代理 Method 对象失败", e);
        }
    }
}
  • 第一段 代码,展示了如何定义接口、实现类,以及如何通过 Proxy.newProxyInstanceInvocationHandler 组合成动态代理。

  • 第二段 是反编译后的 $Proxy0,它是真正实现了 Subject 接口的“代理类”,在每个方法体里只是调用 super.h.invoke(...) 将调用交给 DynamicProxyHandler.invoke(...)

另外,补充一个类似于Retrofit的动态代理

在 JDK 动态代理里,真正的“实现”都写在那段 invoke(...) 里——代理类($Proxy0)本身只会做两件事:

  1. 调用 InvocationHandler.invoke(this, method, args)
  2. 返回 invoke 的结果
/**
 * 1. 接口定义:只声明方法签名,无需任何实现
 */
public interface HelloService {
    /**
     * sayHello 方法
     * @param var1 客户端传入的名字
     * @return 代理生成的问候语
     */
    String sayHello(String var1);
}

/**
 * 2. 主类:演示“无真实实现类”的纯动态代理
 */
public class DynamicProxyNoTargetDemo {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        /**
         * 3. 使用 Proxy.newProxyInstance 创建代理实例
         *    - ClassLoader:用来加载生成的代理类
         *    - interfaces:代理类要实现的接口列表
         *    - InvocationHandler:处理所有方法调用的逻辑
         */
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
                HelloService.class.getClassLoader(),        // ① 指定 ClassLoader
                new Class<?>[]{ HelloService.class },       // ② 指定要实现的接口
                new InvocationHandler() {                   // ③ 定义 InvocationHandler
                    /**
                     * 所有通过 proxy 调用的方法,都会路由到这里
                     *
                     * @param proxy  当前代理对象
                     * @param method 正在调用的接口方法(反射 Method 对象)
                     * @param args   方法参数数组
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 3.1 如果是 Object 自带的方法(toString、hashCode、equals),
                        //     直接执行当前 handler 对象自身的方法,以保持基础行为正常
                        if (method.getDeclaringClass() == Object.class) {
                            return method.invoke(this, args);
                        }
                        // 3.2 针对 sayHello 方法做自定义处理,
                        // 这一段代码就相当于你手写的 `HelloServiceImpl.sayHello(...)`
                        if ("sayHello".equals(method.getName())
                                && args != null && args.length == 1) {
                            String name = (String) args[0];  // 拿到传入的参数
                            // 返回自定义结果,而不需要真实实现类
                            return "[Proxy 自动生成] Hello, " + name + "!";
                        }
                        // 3.3 其他方法一律不支持,抛出异常
                        throw new UnsupportedOperationException(
                                "Unsupported method: " + method.getName());
                    }
                }
        );

        // 4. 通过代理对象调用接口方法,实际会执行 InvocationHandler.invoke(...)
        String result = proxy.sayHello("李四");
        System.out.println(result);
        // 预期输出: [Proxy 自动生成] Hello, 李四!

        // 5. 打印运行时生成的代理类全限定名,通常形如 jdk.proxy1.$Proxy0
        System.out.println("Generated proxy class: " + proxy.getClass().getName());
    }
}

在linux或者git bash中执行命令,生成动态代理的文件

java \
        -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true \
        -cp . \
DynamicProxyNoTargetDemo

动态代理生成的真实的文件,这也是运行时真正调用的文件:

/**
 * 自动生成的动态代理类,继承自 java.lang.reflect.Proxy 并实现用户接口 HelloService
 * 由 JVM 在运行时通过 ProxyGenerator 生成,用于将方法调用委托给 InvocationHandler
 */
public final class $Proxy0 extends Proxy implements HelloService {
    // 缓存原生 Object.hashCode 方法的 Method 对象,用于在代理中转发
    private static final Method m0;
    // 缓存原生 Object.equals 方法的 Method 对象,用于在代理中转发
    private static final Method m1;
    // 缓存原生 Object.toString 方法的 Method 对象,用于在代理中转发
    private static final Method m2;
    // 缓存接口 HelloService.sayHello(String) 方法的 Method 对象,用于在代理中转发
    private static final Method m3;

    /**
     * 构造函数,由 Proxy 架构调用,将用户传入的 InvocationHandler 保存在父类字段 h 中
     * @param h 用户实现的 InvocationHandler 实例
     */
    public $Proxy0(InvocationHandler h) {
        super(h);
    }

    /**
     * 代理 hashCode() 方法的实现,将调用转发给 InvocationHandler
     */
    public final int hashCode() {
        try {
            // 调用 handler.invoke(proxyInstance, Method, args)
            return (Integer) super.h.invoke(this, m0, (Object[]) null);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable t) {
            // 将受检异常包装成 UndeclaredThrowableException
            throw new UndeclaredThrowableException(t);
        }
    }

    /**
     * 代理 equals(Object) 方法的实现,将调用转发给 InvocationHandler
     */
    public final boolean equals(Object obj) {
        try {
            return (Boolean) super.h.invoke(this, m1, new Object[]{obj});
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    /**
     * 代理 toString() 方法的实现,将调用转发给 InvocationHandler
     */
    public final String toString() {
        try {
            return (String) super.h.invoke(this, m2, (Object[]) null);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    /**
     * 代理接口方法 sayHello(String),将调用转发给 InvocationHandler
     */
    public final String sayHello(String name) {
        try {
            return (String) super.h.invoke(this, m3, new Object[]{name});
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    // 在类加载时初始化所有 Method 对象,以便在 invoke 时快速查找
    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("HelloService").getMethod("sayHello", Class.forName("java.lang.String"));
        } catch (NoSuchMethodException e) {
            // 如果方法签名不匹配,抛出 NoSuchMethodError
            throw new NoSuchMethodError(e.getMessage());
        } catch (ClassNotFoundException e) {
            // 如果找不到类,抛出 NoClassDefFoundError
            throw new NoClassDefFoundError(e.getMessage());
        }
    }

    /**
     * 支持 MethodHandles.Lookup API,确保代理类能够访问目标方法的句柄
     */
    private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup lookup) throws IllegalAccessException {
        // 仅在 lookupClass 为 Proxy 且拥有完全权限时,返回真实的 Lookup
        if (lookup.lookupClass() == Proxy.class && lookup.hasFullPrivilegeAccess()) {
            return MethodHandles.lookup();
        } else {
            throw new IllegalAccessException(lookup.toString());
        }
    }
}

JDK 动态代理在运行时按接口列表(和 ClassLoader)生成代理类

  1. 同一套接口,只生成一次

    • 如果多次用同一个接口数组、同一个 ClassLoader 调用 Proxy.newProxyInstance,JVM 只会生成一次对应的 $ProxyN 类,后续都会复用它。
    • 这样可以避免重复生成字节码、节省内存开销。
  2. 不同接口集合,各自生成

    • 当传入新的接口列表(哪怕只有一个新接口),JVM 会为这组接口再生成一个代理类。

    • 例如:

      // 生成第一个代理类:只实现 HelloService
      HelloService p1 = (HelloService) Proxy.newProxyInstance(
        cl, new Class[]{HelloService.class}, handler);
      System.out.println(p1.getClass().getName());  // jdk.proxy1.$Proxy0
      
      // 生成第二个代理类:只实现 ServiceB
      ServiceB    p2 = (ServiceB)    Proxy.newProxyInstance(
        cl, new Class[]{ServiceB.class}, handler);
      System.out.println(p2.getClass().getName());  // jdk.proxy1.$Proxy1
      
      // 生成第三个代理类:同时实现 HelloService + ServiceB
      Object      p3 =                Proxy.newProxyInstance(
        cl, new Class[]{HelloService.class, ServiceB.class}, handler);
      System.out.println(p3.getClass().getName());  // jdk.proxy1.$Proxy2
      
  3. 因此,它可以一次“制造”任意多个代理类

    • 并不是只能对一个类生成,而是对每种接口组合生成或复用一个代理类。
    • 只要你给它不同的接口列表,它就会在内存(或落盘,如果你开启了 -D…ProxyGenerator.saveGeneratedFiles=true)生成相应的 $ProxyX
  4. 一个代理类也可以实现多接口

    • 如上例的 p3,在一次调用里就生成了一个代理类,同时实现 HelloServiceServiceB
    • 这个机制让动态代理能很灵活地“拼接”接口,而不是像静态代理那样,一个代理类只能对应一个接口。

归根结底,JDK 动态代理就两种玩法

  1. 委托给真实实现

    • 先写一个或一批接口实现类(RealSubjectHelloServiceImpl……)

    • InvocationHandler.invoke(...) 里用

      Object result = method.invoke(subject, args);
      

      完成真正的业务调用,前后再插入日志、权限、事务等切面逻辑。

  2. 纯动态“写死”逻辑

    • 根本不依赖任何实现类

    • invoke(...) 里自己写所有业务:

      if ("sayHello".equals(method.getName())) {
        return "[Proxy] Hello, "+args[0];
      }
      

JDK Proxy 本身不会帮助你生成或查找任何真实对象,它唯一做的事情是:

  1. 在运行时生成一个实现了接口的“壳”类($ProxyN),
  2. 把每个接口方法的调用一律转给你提供的 InvocationHandler.invoke(...)

所以,你要么

  • 先有实现,再在 invokemethod.invoke(subject, …) 委托给它;
    要么
  • 直接在 invoke 里实现(或拼装)你想要的行为,省去真实类。

参考文献

juejin.cn/post/685457…

学后检测

一、单项选择题(每题 4 分)

  1. 在静态代理模式中,代理类与被代理类必须具备的共同特征是?
    A. 继承同一父类
    B. 实现同一接口
    C. 拥有完全相同的字段
    D. 处于同一包

    答案:B
    解析: 静态代理要求代理对象与目标对象对调用者表现一致,因此二者必须实现同一个接口以保持行为契约;并非强制继承同一父类或位于同一包。

  2. JDK 动态代理运行时生成的 $Proxy0 实际继承自哪一个类?
    A. java.lang.Object
    B. 目标对象的类
    C. java.lang.reflect.Proxy
    D. java.lang.ClassLoader

    答案:C
    解析: 所有由 ProxyGenerator 生成的代理类都会继承 java.lang.reflect.ProxyProxy 再继承自 Object

  3. InvocationHandler.invoke 方法里直接调用 method.invoke(proxy, args) 最可能导致什么结果?
    A. 方法正常执行
    B. IllegalAccessException
    C. 无限递归直至 StackOverflowError
    D. 返回 null

    答案:C
    解析: proxy 本身还是代理对象,再次调用其方法会再次触发 invoke,形成递归。

  4. 下列哪一项是 JDK 动态代理能够正常工作的前提?
    A. 目标类必须是 final
    B. 目标类实现了至少一个接口
    C. 目标类必须无参构造
    D. 目标类必须位于默认包

    答案:B
    解析: JDK 动态代理基于接口列表生成代理类;若目标类无接口,只能用 CGLIB 等字节码增强方案。

  5. 使用动态代理大幅降低为每个业务类重复编写日志、权限、事务代码的目的体现了哪种设计思想?
    A. 单例模式
    B. 观察者模式
    C. 面向切面编程(AOP)/关注点分离
    D. 责任链模式

    答案:C
    解析: 动态代理让横切关注点(日志、事务等)与核心业务解耦,正是 AOP 思想的典型落地。


二、多项选择题(每题 4 分,多选或少选均不得分)

  1. 静态代理常见缺点包括哪些?
    A. 运行时生成代理类开销大
    B. 一个代理类只能服务一个接口
    C. 需要手写大量重复代理代码
    D. 系统类数量膨胀,增加维护成本

    答案:B C D
    解析: 静态代理在编译期就确定,无法复用或拼接接口,且每个接口都要编写对应代理类,导致代码臃肿。运行时开销反而较小。

  2. 以下哪些场景特别适合用 JDK 动态代理实现?
    A. 记录方法调用日志
    B. 对服务方法统一添加事务边界
    C. 基于注解进行权限校验
    D. 序列化/反序列化对象

    答案:A B C
    解析: 日志、事务、权限都属于横切关注点,可在 invoke 中统一处理;序列化不是代理的直接应用场景。


三、判断题(每题 2 分,正确打 √,错误打 ×)

  1. 静态代理一定比动态代理性能更高。 ×
    解析: 动态代理在 HotSpot JIT 优化后调用开销可忽略不计;性能差异取决于具体场景,不存在“一定”更高。
  2. 多次以相同 ClassLoader 和完全相同接口数组调用 Proxy.newProxyInstance,JVM 只生成一个代理类并复用。
    解析: Proxy 内部使用缓存(key 为 ClassLoader + 接口列表)避免重复生成字节码。

四、简答题(共 10 分)

  1. 写出 Proxy.newProxyInstance 的三个核心参数及各自作用。

    • ClassLoader loader:指定生成的代理类应由哪个类加载器加载。
    • Class<?>[] interfaces:新代理类需要实现的接口列表,决定了代理对象能暴露哪些方法。
    • InvocationHandler h:方法调用的统一分发入口;所有接口方法都会回调到 h.invoke(...) 由开发者自定义处理逻辑。

    答案要点: 三个参数名称与职责必须对应说明;阐述它们如何协同决定代理类的生成与行为。

五、实践题(共 20 分)

  1. 动手编码

    • 定义接口 CalcService(含 int add(int a,int b))。

    • 编写实现类 CalcServiceImpl

    • 使用 JDK 动态代理创建一个代理对象,在每次调用 add 前后打印参数与返回值。

    • main 方法中调用 add(5, 7),输出示例:

      >> entering add: a=5, b=7
      << exiting add: result=12
      

1. 接口 CalcService

public interface CalcService {
    int add(int a, int b);
}

2. 实现类 CalcServiceImpl

public class CalcServiceImpl implements CalcService {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}

3. 动态代理处理器 LogInvocationHandler

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

public class LogInvocationHandler implements InvocationHandler {

    /** 真正执行业务的对象 */
    private final Object target;

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

    /** 统一分发所有方法调用 */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置日志
        if ("add".equals(method.getName()) && args != null && args.length == 2) {
            System.out.printf(">> entering add: a=%s, b=%s%n", args[0], args[1]);
        }

        // 委托给真实对象
        Object result = method.invoke(target, args);

        // 后置日志
        if ("add".equals(method.getName())) {
            System.out.printf("<< exiting add: result=%s%n", result);
        }
        return result;
    }
}

4. 测试入口 Main

import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        // 1. 创建真实对象
        CalcService real = new CalcServiceImpl();

        // 2. 创建代理对象
        CalcService proxy = (CalcService) Proxy.newProxyInstance(
                CalcService.class.getClassLoader(),
                new Class<?>[]{CalcService.class},
                new LogInvocationHandler(real)
        );

        // 3. 调用业务方法,触发日志
        int sum = proxy.add(5, 7);
        System.out.println("sum = " + sum); // 这里额外打印一次,便于确认
    }
}