一、为什么需要代理类?

想象一个现实生活中的场景:你是一个明星,有很多商业活动和采访邀约。但你不会自己去处理所有琐事(比如谈合同、安排行程、应付记者),而是聘请一位经纪人

  • 你(明星):核心人物,负责最核心的工作(上台表演、接受采访)。

  • 经纪人:代替你处理一切前后期工作。他在你工作之前(筛选活动、谈价钱)、之后(收钱、处理舆情)做一些额外的事情,但他最终的目的还是让你能顺利工作。

在程序中,代理模式就是为其他对象(“明星”)提供一个代理(“经纪人”)以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。

为什么要这样做?

  1. 职责清晰:让“明星”只关注核心业务,其他公共逻辑(如日志、事务、安全)交给“经纪人”处理。

  2. 功能增强:在不修改“明星”原有代码的情况下,“经纪人”可以额外添加新的功能(如记录活动日志),这符合 “开放-封闭原则”

  3. 控制访问:“经纪人”可以决定是否将请求转发给“明星”,例如进行权限校验。


二、Java 中代理类的实现方式

Java 主要提供了两种代理实现方式:

  1. 静态代理:在编译期就确定代理关系,程序员手动创建代理类。

  2. 动态代理:在运行时动态生成代理类,非常灵活。


三、场景与代码说明

场景:为一个服务方法添加性能监控

假设我们有一个核心服务接口 UserService,我们想在执行其方法前后记录耗时,但又不想修改原有的业务代码。

1. 定义接口和真实对象(明星)
// 1. 服务接口 (明星的工作规范)
public interface UserService {
    void addUser();
    void deleteUser();
}

// 2. 真实对象,实现接口 (真正的明星)
public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        // 模拟核心业务逻辑
        System.out.println("添加用户...");
        try {
            Thread.sleep(100); // 模拟耗时操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void deleteUser() {
        // 模拟核心业务逻辑
        System.out.println("删除用户...");
        try {
            Thread.sleep(200); // 模拟耗时操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
2. 静态代理 (手动聘请的经纪人)
// 静态代理类,也必须实现同一个接口
public class UserServiceStaticProxy implements UserService {

    // 持有真实对象的引用
    private UserService target;

    public UserServiceStaticProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void addUser() {
        // 【前置增强】- 经纪人的工作
        long startTime = System.currentTimeMillis();
        System.out.println("记录日志:开始执行 addUser()");

        // 【核心业务】- 让明星工作
        target.addUser();

        // 【后置增强】- 经纪人的工作
        long endTime = System.currentTimeMillis();
        System.out.println("记录日志:addUser() 执行完成,耗时:" + (endTime - startTime) + "ms");
    }

    @Override
    public void deleteUser() {
        // 同样的模式...
        long startTime = System.currentTimeMillis();
        System.out.println("记录日志:开始执行 deleteUser()");
        target.deleteUser();
        long endTime = System.currentTimeMillis();
        System.out.println("记录日志:deleteUser() 执行完成,耗时:" + (endTime - startTime) + "ms");
    }
}

使用静态代理:

public class StaticProxyDemo {
    public static void main(String[] args) {
        // 创建真实对象(明星)
        UserService realStar = new UserServiceImpl();
        // 创建代理对象(经纪人),并让经纪人知道为谁工作
        UserService agent = new UserServiceStaticProxy(realStar);

        // 客户端和经纪人打交道,感觉不到明星的存在
        agent.addUser();
        System.out.println("----------");
        agent.deleteUser();
    }
}

输出结果:

text

记录日志:开始执行 addUser()
添加用户...
记录日志:addUser() 执行完成,耗时:100ms
----------
记录日志:开始执行 deleteUser()
删除用户...
记录日志:deleteUser() 执行完成,耗时:200ms

静态代理的缺点

  • 如果接口有很多方法,代理类会变得非常臃肿,每个方法都要写重复的增强代码。

  • 如果有很多类需要代理,就需要为每一个类都创建一个代理类,难以维护。


3. 动态代理 (一个万能经纪人)

JDK 动态代理利用了反射机制,在程序运行时动态地创建代理类。它有一个“万能经纪人”,可以代理任何实现了接口的“明星”。

创建一个“万能经纪人”(调用处理器)

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

// 万能经纪人,实现 InvocationHandler 接口
public class PerformanceMonitorHandler implements InvocationHandler {

    // 持有真实对象(任何明星都可以)
    private Object target;

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

    /**
     * 所有通过代理对象调用的方法,都会自动走到这个 invoke 方法里来
     * @param proxy  代理对象本身(通常很少用)
     * @param method 当前被调用的方法(addUser, deleteUser...)
     * @param args   调用方法时传入的参数
     * @return       原方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 【前置增强】
        long startTime = System.currentTimeMillis();
        System.out.println("记录日志:开始执行 " + method.getName() + "()");

        // 【核心业务】:利用反射机制,调用真实对象的方法
        Object result = method.invoke(target, args);

        // 【后置增强】
        long endTime = System.currentTimeMillis();
        System.out.println("记录日志:" + method.getName() + "() 执行完成,耗时:" + (endTime - startTime) + "ms");

        return result; // 返回原方法执行的结果
    }
}

使用动态代理:

import java.lang.reflect.Proxy;

public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 1. 创建真实对象(明星)
        UserService realStar = new UserServiceImpl();

        // 2. 创建“万能经纪人”,并告诉他代理谁
        InvocationHandler handler = new PerformanceMonitorHandler(realStar);

        // 3. 使用Proxy类的工厂方法,动态地创建一个代理对象
        //    参数:类加载器、代理类要实现的接口、调用处理器
        UserService agent = (UserService) Proxy.newProxyInstance(
                realStar.getClass().getClassLoader(), // 用明星的类加载器
                realStar.getClass().getInterfaces(),  // 明星实现的所有接口(这样经纪人才有同样能力)
                handler                               // 我们的万能经纪人
        );

        // 4. 使用代理对象
        // 注意:这里的 agent 不是 UserServiceStaticProxy 的实例,而是在运行时动态生成的一个类$Proxy0的实例
        agent.addUser();
        System.out.println("----------");
        agent.deleteUser();

        // 可以看下代理对象的类名
        System.out.println("代理对象的类型:" + agent.getClass().getName());
    }
}

输出结果(与静态代理一致)

记录日志:开始执行 addUser() 添加用户...
记录日志:addUser() 执行完成,耗时:100ms ----------
记录日志:开始执行 deleteUser() 删除用户...
记录日志:deleteUser() 执行完成,耗时:200ms 代理对象的类型:com.sun.proxy.$Proxy0 // 这是一个动态生成的类

动态代理的优点

  • 通用性强:一个 InvocationHandler 可以代理所有类型的对象,只要它有接口。

  • 易于维护:不需要为每个类都创建代理类,避免代码冗余。


四、总结与对比

特性 静态代理 动态代理
实现方式 程序员手动编写代理类 JDK 运行时动态生成代理类
灵活性 差,一个代理类只能代理一种类型 好,一个处理类可代理多种类型
可维护性 差,接口改动时代理类也需修改 好,逻辑集中在一个处理类中
技术要求 低,简单易懂 高,需要理解反射机制
应用场景 代理类较少、方法较简单时 主流应用,如Spring AOP、RPC框架

为什么需要代理?—— 再次总结

  1. 解耦与非侵入性:将核心业务逻辑(如添加用户)和横切关注点(如日志、事务、安全)分离。你可以增强一个类的功能而无需修改其代码。

  2. 复用公共逻辑:像日志、性能监控这样的代码,通过代理可以写一次,然后应用到任何需要的地方。

  3. 权限控制:在代理中,可以方便地加入权限判断逻辑,决定是否调用真实对象的方法。

现实世界的应用

  • Spring AOP:其底层就是基于动态代理来实现的面向切面编程,用于处理事务(@Transactional)、日志、安全等。

  • RPC框架:远程服务调用(如Dubbo)中,客户端拿到的“服务”实际上是一个本地代理,它负责将方法调用封装成网络请求并发送给远端的服务器。

  • MyBatis:Mapper接口没有实现类,但MyBatis通过动态代理为你生成了实现类,代理方法会执行SQL并返回结果。

更多推荐