36_Java设计模式之代理模式

Java设计模式之代理模式

文章目录

前言

我们在日常开发中经常遇到这样的需求:在不修改原始类代码的前提下,为其添加额外的功能,比如记录日志、权限校验、性能统计等。直接侵入原有代码显然不可取,而代理模式(Proxy Pattern)正是解决这类问题的利器。Java中的代理模式分为三种实现方式:静态代理JDK动态代理CGLIB代理

代理模式的核心价值:它体现了面向对象设计中的"单一职责原则"和"开闭原则"。业务类只关心业务逻辑,日志、事务、权限等横切关注点交给代理类处理------这就是AOP(面向切面编程)的思想基础。可以说,不理解代理模式,就不可能真正理解Spring AOP、MyBatis Mapper、RPC框架等Java生态的核心组件。

一、静态代理

代理对象与目标对象实现同一接口,在代理对象中调用目标对象并添加增强逻辑:

java 复制代码
// 公共接口
public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}

// 目标对象(真实业务)
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("添加用户: " + username);
    }

    @Override
    public void deleteUser(String username) {
        System.out.println("删除用户: " + username);
    }
}

// 静态代理类
public class UserServiceProxy implements UserService {
    private UserService target;  // 持有目标对象引用

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

    @Override
    public void addUser(String username) {
        System.out.println("[日志] 开始添加用户...");
        long start = System.currentTimeMillis();

        target.addUser(username);  // 调用目标方法

        long end = System.currentTimeMillis();
        System.out.println("[日志] 添加完成,耗时: " + (end - start) + "ms");
    }

    @Override
    public void deleteUser(String username) {
        System.out.println("[权限] 校验删除权限...");
        target.deleteUser(username);
    }
}

// 使用
public class Client {
    public static void main(String[] args) {
        UserService service = new UserServiceProxy(new UserServiceImpl());
        service.addUser("张三");
        service.deleteUser("张三");
    }
}

优点 :代码清晰,可以对不同方法做不同的增强。

缺点:每个需要代理的类都要手写一个代理类,接口增加方法时代理类也要同步修改,维护成本高。

静态代理还有价值吗? 虽然动态代理更灵活,但静态代理在某些场景仍有其独特优势:当不同方法需要完全不同的增强逻辑时(如addUser需要日志,deleteUser需要权限校验),静态代理可以针对每个方法写不同的前置/后置逻辑,代码意图非常明确。动态代理的InvocationHandler中虽然也能通过method.getName()来区分,但会变成一个大的if-else。所以静态代理适合代理方法少、增强逻辑各异的场景,动态代理适合方法多、增强逻辑统一的场景。

二、JDK动态代理

利用java.lang.reflect.ProxyInvocationHandler在运行时动态生成代理类,无需手写代理类:

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 通用的日志处理器
public class LogInvocationHandler implements InvocationHandler {
    private Object target;  // 目标对象

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        String methodName = method.getName();
        System.out.println("[开始] " + methodName);

        long start = System.currentTimeMillis();
        // 反射调用目标方法
        Object result = method.invoke(target, args);
        long end = System.currentTimeMillis();

        System.out.println("[结束] " + methodName
                + " 耗时: " + (end - start) + "ms");
        return result;
    }

    // 工厂方法:创建代理对象
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target, Class<T> interfaceType) {
        return (T) Proxy.newProxyInstance(
                interfaceType.getClassLoader(),
                new Class<?>[]{interfaceType},
                new LogInvocationHandler(target)
        );
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        UserService service = LogInvocationHandler.createProxy(
                new UserServiceImpl(), UserService.class);
        service.addUser("李四");
        service.deleteUser("李四");
    }
}

运行原理Proxy.newProxyInstance()在JVM内存中动态生成一个代理类的字节码,该代理类实现了指定接口。方法调用时会路由到InvocationHandler.invoke()

限制 :JDK动态代理要求目标对象必须实现至少一个接口 ,因为生成的代理类继承自Proxy(Java不支持多继承),只能通过实现接口来代理。

深入理解Proxy类 :你可以在代码中添加System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true")(Java 8用sun.misc.ProxyGenerator.saveGeneratedFiles),运行后JDK会将生成的代理类字节码保存到磁盘。反编译后你会发现,代理类实现了你指定的所有接口,每个方法内部都调用了InvocationHandler.invoke()。这种"不可见"的代理是很多人觉得动态代理"神秘"的原因------其实不过是用字节码技术帮我们自动生成了静态代理类。

三、CGLIB代理

当目标对象没有实现接口时,使用CGLIB(Code Generation Library)通过继承方式生成代理子类:

java 复制代码
// 没有实现接口的普通类
public class OrderService {
    public void createOrder(String orderId) {
        System.out.println("创建订单: " + orderId);
    }

    public void cancelOrder(String orderId) {
        System.out.println("取消订单: " + orderId);
    }
}

// CGLIB代理(需要引入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 CglibProxyFactory implements MethodInterceptor {
    private Object target;

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

    // 创建代理对象
    public Object getProxyInstance() {
        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("[事务] 开启事务...");
        Object result = method.invoke(target, args);
        System.out.println("[事务] 提交事务...");
        return result;
    }

    public static void main(String[] args) {
        OrderService proxy = (OrderService)
                new CglibProxyFactory(new OrderService()).getProxyInstance();
        proxy.createOrder("ORD-20260529");
    }
}

工作原理:CGLIB底层使用ASM框架操作字节码,生成目标类的子类,通过方法重写实现拦截。

限制 :无法代理final类和方法(因为final不可被继承/重写)。Spring在目标有接口时默认用JDK动态代理,没有接口时用CGLIB。

CGLIB代理的性能陷阱 :虽然CGLIB创建代理对象的速度比JDK慢(因为需要生成字节码),但方法调用的执行效率比JDK的反射调用高。不过从Java 7开始,JDK动态代理使用了MethodHandle优化,反射性能已大幅提升。如果你的应用频繁创建代理对象,JDK动态代理可能更合适;如果是创建少数长生命周期的代理对象并高频调用其方法,CGLIB可能更优。当然,在绝大多数业务场景中,两者的性能差异可以忽略不计------选型更应该关注"有没有接口"这个硬性条件。

四、Spring AOP中的代理

Spring AOP将代理选择自动化,开发者只需写切面逻辑:

java 复制代码
@Aspect
@Component
public class LogAspect {

    @Around("execution(* com.example..*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("[AOP前置] " + joinPoint.getSignature());
        Object result = joinPoint.proceed();
        System.out.println("[AOP后置] " + joinPoint.getSignature());
        return result;
    }
}

Spring会根据条件自动选择JDK动态代理(有接口时)或CGLIB(无接口时)。也可以配置proxy-target-class=true强制使用CGLIB。

五、三种代理方式对比

特性 静态代理 JDK动态代理 CGLIB代理
是否需手写代理类
是否需实现接口
代理原理 硬编码 接口反射 子类继承
性能(调用) 略低(反射)
性能(创建) 无需创建
final方法 可代理 可代理 不可代理

总结

代理模式是现代Java框架的基石,Spring AOP和MyBatis Mapper都是代理模式的典型应用。三种实现各有适用场景:静态代理 适合代理类少、逻辑固定的场景;JDK动态代理 是接口驱动开发的最佳拍档;CGLIB弥补了无接口场景的空白。理解代理模式,对于深入掌握Spring框架至关重要。

一个实用的面试答题框架 :当面试官问"讲讲代理模式",建议你按这个思路回答------先说"代理模式的核心目的是控制对象访问和增强功能",再分三种实现讲:静态代理怎么手写,JDK动态代理怎么用Proxy.newProxyInstance,CGLIB怎么通过继承生成子类。然后自然过渡到Spring AOP是如何在这两者之间自动选择的。最后提一句"实际开发中我们通常通过Spring AOP的@Aspect声明式使用代理,很少手动编码"。这样的回答既展现了知识广度,又体现了实战经验。

✅ 亮点总结

  • 静态代理 → JDK动态代理 → CGLIB代理的完整演进,覆盖了代理模式的三种实现形态
  • Spring AOP自动代理选择机制(有接口=JDK,无接口=CGLIB)与 proxy-target-class 配置详解
  • MyBatis Mapper代理------只写接口不写实现类,动态代理在背后生成代理对象执行SQL
  • 三种代理方式的全维度对比表(手写成本、接口要求、代理原理、性能差异、final方法限制)
  • 代理模式与AOP的天然契合------将横切关注点(日志、事务、权限)从业务代码中剥离

适用场景

  • 微服务间调用------RPC框架用动态代理将远程调用伪装成本地方法调用
  • 延迟加载------Hibernate的懒加载代理对象,只在真正使用时才发起数据库查询
  • 权限控制------在代理层校验用户权限,拒绝非法请求进入核心业务逻辑

扩展方向

  • Spring AOP源码跟踪JdkDynamicAopProxy.invoke()InterceptorChainProceedingJoinPoint 的完整调用链
  • AspectJ编译期织入:对比Spring AOP运行时代理,了解编译期/类加载期织入的差异(推荐阅读下一篇:Java设计模式之观察者模式)
  • Java动态代理的局限性:只能代理接口,了解ByteBuddy等新一代代理框架的优势
相关推荐
盒马盒马1 小时前
Rust:String
java·前端·rust
许彰午1 小时前
35_Java设计模式之工厂模式
java·开发语言·设计模式
uoKent1 小时前
项目整理——设计模式
设计模式·软件需求
凡人叶枫1 小时前
Effective C++ 条款32:确定你的 public 继承塑模出 is-a(是一种)关系
java·linux·开发语言·c++·嵌入式开发
小杨互联网1 小时前
Jar反编译逆向2.0教程实战
java·jar·java反编译·jar反编译·java逆向·源码还原
爱码少年1 小时前
Spring Boot 文件上传下载完整指南:从基础到高级实践
java·spring boot
Flittly1 小时前
【AgentScope Java新手村系列】(7)子Agent编排
java·spring boot·笔记·spring·ai
一个做软件开发的牛马2 小时前
Spring Boot Web 开发实战:RESTful API 设计、统一异常处理、参数校验与拦截器
java·后端
yurenpai(27届找实习中)2 小时前
Feed 流推送与附近商户:从推模式到 GeoHash,一条 Timeline 的完整旅程
java·数据库·oracle·feed