36_Java设计模式之代理模式

Java设计模式之代理模式

前言

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

代理模式的核心价值:它体现了面向对象设计中的"单一职责原则"和"开闭原则"。业务类只关心业务逻辑,日志、事务、权限等横切关注点交给代理类处理——这就是AOP(面向切面编程)的思想基础。可以说,不理解代理模式,就不可能真正理解Spring AOP、MyBatis Mapper、RPC框架等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在运行时动态生成代理类,无需手写代理类:

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)通过继承方式生成代理子类:

// 没有实现接口的普通类
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将代理选择自动化,开发者只需写切面逻辑:

@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等新一代代理框架的优势
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值