Java案例中的代理模式怎么用?

wen java案例 3

Java案例中的代理模式怎么用?一文讲透原理、场景与实战代码

目录导读

  1. 代理模式是什么?核心思想一句话讲清
  2. 代理模式的三大角色与UML图解
  3. Java代理的四种实现方式对比
  4. 静态代理实现日志记录(最易懂的入门)
  5. 动态代理JDK Proxy实现权限校验
  6. CGLIB代理实现无接口类的延迟加载
  7. 代理模式在实际框架中的应用(Spring AOP / MyBatis)
  8. 常见问题Q&A(面试高频)
  9. 什么时候该用代理模式?

代理模式是什么?核心思想一句话讲清

代理模式(Proxy Pattern) 是一种结构型设计模式,它允许你提供一个代理对象来控制对另一个对象的访问。
通俗地说:你不想直接访问“本人”,于是找了一个“代理人”来替你完成某些操作,并在操作前后附加额外逻辑。

Java案例中的代理模式怎么用?

举个生活案例:明星的经纪人就是代理——粉丝要找明星演出,必须先通过经纪人谈档期、谈费用,经纪人还可以在签约前后做宣传、收尾款,而明星只负责上台表演。

在Java中,代理模式常用于:日志记录、权限控制、延迟加载、远程调用、事务管理等场景。


代理模式的三大角色与UML图解

代理模式包含三个核心角色:

角色 定义 例子
Subject(主题接口) 定义真实对象和代理对象共同的接口 Star 接口
RealSubject(真实主题) 真正执行业务逻辑的对象 RealStar
Proxy(代理) 持有真实对象的引用,控制对它的访问,并添加额外操作 AgentProxy
┌─────────────┐      implements      ┌───────────────┐
│  Subject     │◄─────────────────────│  Proxy        │
│  (接口)       │                      │  -RealSubject │
│ +operation() │                      │ +operation()  │
└─────────────┘                      └───────┬───────┘
                                             │
                                    ┌────────▼───────┐
                                    │  RealSubject   │
                                    │ +operation()   │
                                    └────────────────┘

Java代理的四种实现方式对比

实现方式 是否需要接口 原理 性能 适用场景
静态代理 需要接口 手动编写代理类 较快 代理类较少且固定
JDK动态代理 需要接口 运行时生成代理对象 面向接口编程
CGLIB动态代理 不需要接口 生成子类覆盖方法 较快(低版本略慢) 无接口类
虚拟代理/远程代理 视情况 内部逻辑控制 取决于实现 延迟加载、RMI

案例一:静态代理实现日志记录(最易懂的入门)

场景:现有UserService接口,需要为每个方法添加执行前后日志。

// 1. 主题接口
public interface UserService {
    void addUser(String name);
    void deleteUser(int id);
}
// 2. 真实主题
public class UserServiceImpl implements UserService {
    public void addUser(String name) {
        System.out.println("添加用户: " + name);
    }
    public void deleteUser(int id) {
        System.out.println("删除用户ID: " + id);
    }
}
// 3. 静态代理类
public class UserServiceProxy implements UserService {
    private UserService target;
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    public void addUser(String name) {
        System.out.println("[日志] 开始执行addUser, 参数: " + name);
        target.addUser(name);
        System.out.println("[日志] addUser执行结束");
    }
    public void deleteUser(int id) {
        System.out.println("[日志] 开始执行deleteUser, 参数: " + id);
        target.deleteUser(id);
        System.out.println("[日志] deleteUser执行结束");
    }
}
// 4. 客户端调用
public class Client {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(userService);
        proxy.addUser("张三");
        proxy.deleteUser(1);
    }
}

输出

[日志] 开始执行addUser, 参数: 张三  
添加用户: 张三  
[日志] addUser执行结束  
[日志] 开始执行deleteUser, 参数: 1  
删除用户ID: 1  
[日志] deleteUser执行结束  

优点:简单直观,不依赖第三方库。
缺点:每个真实类都需要手动编写一个代理类,当业务类很多时代码冗余严重。


案例二:动态代理JDK Proxy实现权限校验

场景:为Calculator接口的所有方法添加权限校验(只有admin用户能调用)。

// 1. 主题接口
public interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
}
// 2. 真实主题
public class CalculatorImpl implements Calculator {
    public int add(int a, int b) { return a + b; }
    public int subtract(int a, int b) { return a - b; }
}
// 3. 调用处理器(代理逻辑)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class SecurityProxyHandler implements InvocationHandler {
    private Object target;
    private String currentUser;
    public SecurityProxyHandler(Object target, String currentUser) {
        this.target = target;
        this.currentUser = currentUser;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (!"admin".equals(currentUser)) {
            throw new SecurityException("权限不足,只有admin用户可以执行操作");
        }
        System.out.println("[权限校验] 用户 " + currentUser + " 通过校验");
        Object result = method.invoke(target, args);
        return result;
    }
    // 工厂方法创建代理对象
    public static Object createProxy(Object target, String user) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new SecurityProxyHandler(target, user)
        );
    }
}
// 4. 客户端
public class Client {
    public static void main(String[] args) {
        Calculator realCalc = new CalculatorImpl();
        // 用普通用户尝试
        Calculator proxy1 = (Calculator) SecurityProxyHandler.createProxy(realCalc, "guest");
        try {
            proxy1.add(3, 5);
        } catch (SecurityException e) {
            System.out.println(e.getMessage());
        }
        // 用admin用户
        Calculator proxy2 = (Calculator) SecurityProxyHandler.createProxy(realCalc, "admin");
        System.out.println("结果: " + proxy2.add(3, 5));
    }
}

输出

权限不足,只有admin用户可以执行操作  
[权限校验] 用户 admin 通过校验  
结果: 8  

核心机制Proxy.newProxyInstance 在运行时动态生成一个实现了Calculator接口的代理类,所有方法调用都会转发到invoke()方法中。
注意:JDK动态代理要求目标类必须实现接口


案例三:CGLIB代理实现无接口类的延迟加载

场景:一个大型对象HeavyObject的创建非常耗时,希望在使用时才真正初始化。

// 1. 需要被代理的类(无接口)
public class HeavyObject {
    public HeavyObject() {
        System.out.println("HeavyObject 正在初始化... (消耗大量资源)");
        // 模拟耗时操作
        try { Thread.sleep(2000); } catch (InterruptedException e) { }
    }
    public void doWork() {
        System.out.println("开始执行实际业务...");
    }
}
// 2. CGLIB延迟加载代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class LazyProxyFactory {
    public static Object createLazyProxy(Class<?> targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback(new LazyInterceptor());
        return enhancer.create();
    }
    private static class LazyInterceptor implements MethodInterceptor {
        private Object realObject = null;
        @Override
        public Object intercept(Object obj, Method method, Object[] args,
                                MethodProxy proxy) throws Throwable {
            if (realObject == null) {
                realObject = targetClass.newInstance(); // 首次真正调用时创建
            }
            return method.invoke(realObject, args);
        }
    }
}
// 3. 客户端
public class Client {
    public static void main(String[] args) {
        HeavyObject proxy = (HeavyObject) LazyProxyFactory.createLazyProxy(HeavyObject.class);
        System.out.println("代理对象创建完成,但尚未初始化真实对象");
        // 直到调用方法才初始化
        proxy.doWork(); // 此时才输出 "HeavyObject 正在初始化..."
    }
}

输出

代理对象创建完成,但尚未初始化真实对象  
HeavyObject 正在初始化... (消耗大量资源)  
开始执行实际业务...  

优势:不要求被代理类实现接口,通过继承生成子类代理。
注意:CGLIB不能代理final类或final方法。


代理模式在实际框架中的应用

1 Spring AOP 的核心实现就是动态代理

Spring AOP(面向切面编程)底层使用JDK动态代理或CGLIB代理:

  • 如果目标对象实现了接口,默认使用JDK动态代理
  • 如果目标对象没有实现接口,则使用CGLIB代理
// Spring AOP 声明式事务管理示例
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
    // 这里的@Transactional 就是通过代理在方法前后添加了开启事务、提交/回滚的逻辑
}

代理做的事情

  • 方法前:beginTransaction()
  • 方法后:commit()rollback()
  • 异常时:rollback()

2 MyBatis Mapper接口的代理

MyBatis中,我们定义的Mapper接口实际上没有实现类,MyBatis框架通过JDK动态代理为每个Mapper接口生成代理对象:

// 写一个接口,不需要实现类
public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User findById(int id);
}
// 使用时
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findById(1); // 实际调用了代理对象的invoke()

代理内部逻辑:将方法调用解析为SQL语句,执行数据库查询,将结果集映射为对象。


常见问题Q&A(面试高频)

Q1:静态代理和动态代理的核心区别是什么?
A:静态代理是在编译期手动编写代理类,一个代理类只能代理一个接口;动态代理是在运行期由JVM自动生成代理类,一个处理器可以代理任意接口。

Q2:JDK动态代理为什么必须要求目标类实现接口?
A:因为JDK动态代理生成的代理类默认继承Proxy类(Java单继承限制),只能通过实现目标接口的方式来拥有与目标类相同的方法签名,如果目标类没有接口,则无法使用JDK代理。

Q3:CGLIB能代理final类或final方法吗?
A:不能,CGLIB通过生成子类来代理,final类不能被继承,final方法不能被重写,因此无法代理。

Q4:什么时候应该选择JDK动态代理 vs CGLIB?
A:优先选择JDK动态代理(因为它由Java原生支持,性能稳定),只有当目标类没有实现任何接口时,才使用CGLIB,在Spring中,默认会智能切换。

Q5:代理模式和装饰器模式有什么区别?
A:二者结构类似,但意图不同

  • 代理模式:控制对目标对象的访问(权限、延迟加载、远程等)
  • 装饰器模式:动态增强目标对象的功能(如给咖啡加糖、加奶)
    通俗理解:代理是“我替你做,但最后还得你做”;装饰器是“我帮着你一起做更多”。

什么时候该用代理模式?

场景 推荐代理类型
需要为多个接口方法添加统一日志、监控、异常处理 JDK动态代理
需要缓存/延迟加载大型对象 CGLIB延迟代理
需要对无接口的第三方类进行功能增强 CGLIB
需要实现远程对象调用(RPC/RMI) 远程代理
需要控制对敏感对象的访问(权限、安全) 动态代理或静态代理

一句话选择原则
有接口用JDK动态代理,无接口用CGLIB动态代理;
代理类少且不常变时可用静态代理,但大多数现代项目直接上动态代理更灵活。


写在最后

代理模式是Java中应用最广泛的设计模式之一,从Spring AOP到MyBatis,从Hibernate延迟加载到RPC框架,处处可见它的身影。
理解并掌握静态代理 → JDK动态代理 → CGLIB代理的演进过程,能让你的代码更具扩展性。
下次面试官问“代理模式怎么用”,你不仅能说出理论,还能给出三个完整案例——这,就是你的核心竞争力。

记住:代理不创造新功能,它只让你原本的代码“被看到”之前,有机会做点额外的事。

上一篇如何用Java案例实现配置文件解析?

下一篇当前分类已是最新一篇

抱歉,评论功能暂时关闭!