结构型设计模式-代理模式

3 阅读33分钟

概述

代理模式(Proxy Pattern)是GoF设计模式中结构型模式的重要一员,其核心定义为:为其他对象提供一种代理,以控制对这个对象的访问。这句话虽短,却蕴含了面向对象设计中极为重要的间接性思想——在客户端与目标对象之间插入一个中间层,从而实现对目标对象访问的精细化管控。

代理模式解决的并非单一问题,而是一类具有共性的横切关注点(Cross-cutting Concerns):当我们需要在方法调用前后统一添加日志记录、性能监控、事务管理、权限校验、缓存加速、延迟加载等逻辑时,直接修改业务代码会带来严重的代码侵入和重复。代理模式通过将增强逻辑封装在代理对象中,使得业务对象可以专注于核心职责,实现了关注点分离。

在Java生态中,代理模式更是无处不在。从最基础的静态代理,到JDK内置的动态代理,再到基于字节码生成的高性能CGLIB代理,代理的实现方式从编译期固定走向运行时动态生成,灵活性不断提升。更进一步,在分布式系统中,代理模式的思想被扩展到远程服务调用(RPC)、微服务网关、数据库中间件等领域,成为构建大规模分布式架构的基石。

本文将带领读者从代理模式的基本定义出发,通过循序渐进的代码演进,深入剖析JDK、Spring、MyBatis、Dubbo等主流框架中的代理应用,并专门设置分布式环境下的代理实践章节,最终通过六个典型场景的完整Demo与图表分析,帮助读者达到对代理模式的专家级理解。


一、模式定义与结构

1.1 GoF标准定义

代理模式(Proxy Pattern):为其他对象提供一种代理,以控制对这个对象的访问。

——《设计模式:可复用面向对象软件的基础》

1.2 UML类图

classDiagram
    class Subject {
        <<interface>>
        +request() void
    }
    
    class RealSubject {
        +request() void
    }
    
    class Proxy {
        -realSubject: RealSubject
        +request() void
    }
    
    Subject <|.. RealSubject : 实现
    Subject <|.. Proxy : 实现
    Proxy o-- RealSubject : 持有引用

1.3 类图详解

上图展示的是代理模式的标准结构,其中包含三个核心角色:

Subject(抽象主题):定义了RealSubject和Proxy的共同接口,这样在任何需要RealSubject的地方都可以使用Proxy进行替换。这种“接口一致性”是代理模式能够透明化地控制对象访问的前提。在实际编码中,Subject通常以Java接口或抽象类的形式存在,它声明了客户端所关心的业务方法。

RealSubject(真实主题):实现了Subject接口的具体业务类,它包含了真正的业务逻辑。在没有代理的情况下,客户端会直接与RealSubject交互。引入代理后,RealSubject成为被保护、被增强的目标对象,不再直接暴露给客户端。

Proxy(代理):同样实现Subject接口,并在内部持有一个RealSubject的引用(或某种能够定位RealSubject的机制)。当客户端调用代理对象的request()方法时,代理可以在调用真实对象的对应方法之前或之后执行额外的操作,比如日志记录、权限检查、延迟初始化等。代理控制访问的具体方式包括:转发请求、拒绝请求、缓存结果、延迟创建真实对象等。

代理对象如何控制访问?关键在于Proxy中request()方法的实现逻辑。例如,保护代理会在调用前检查当前用户的权限,只有通过校验才会执行realSubject.request();虚拟代理则先判断realSubject是否为null,若是则先创建它再调用;远程代理则负责将方法调用转换为网络请求发送到远端服务器。正是因为Subject接口的统一,客户端完全感知不到自己是在与代理对象还是真实对象交互,这种透明性是代理模式的精髓所在。


二、代码演进与实现

为了让读者透彻理解代理模式的实现原理,本节将按照从简单到复杂的顺序,展示代理模式的多种实现方式。

2.1 不使用模式的原始代码及其问题

// 业务接口
interface UserService {
    void addUser(String username);
    String getUser(String id);
}

// 真实业务实现
class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("执行添加用户逻辑:" + username);
    }

    @Override
    public String getUser(String id) {
        System.out.println("执行查询用户逻辑:" + id);
        return "User[id=" + id + ", name=test]";
    }
}

// 客户端直接调用
public class NoProxyDemo {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        
        // 需求变更:需要在每个方法执行前后记录日志和耗时
        long start = System.currentTimeMillis();
        System.out.println("开始调用addUser方法");
        userService.addUser("张三");
        System.out.println("结束调用addUser方法,耗时:" + (System.currentTimeMillis() - start) + "ms");
        
        start = System.currentTimeMillis();
        System.out.println("开始调用getUser方法");
        String user = userService.getUser("1001");
        System.out.println("结束调用getUser方法,耗时:" + (System.currentTimeMillis() - start) + "ms");
    }
}

问题分析:上述代码存在明显的代码重复和侵入性问题。如果业务方法增多,每个调用点都需要手写相同的日志和计时代码,违反DRY原则。更严重的是,这些增强逻辑与业务逻辑强耦合,当需要调整日志格式或增加事务控制时,必须修改所有调用点。这正是代理模式要解决的核心痛点。

2.2 静态代理模式重构

// Subject接口
interface UserService {
    void addUser(String username);
    String getUser(String id);
}

// RealSubject:真实业务实现
class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("执行添加用户逻辑:" + username);
    }

    @Override
    public String getUser(String id) {
        System.out.println("执行查询用户逻辑:" + id);
        return "User[id=" + id + ", name=test]";
    }
}

// Proxy:代理类,实现UserService接口并持有真实对象的引用
class UserServiceProxy implements UserService {
    private UserService target; // 持有真实主题的引用
    
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    
    @Override
    public void addUser(String username) {
        long start = System.currentTimeMillis();
        System.out.println("[日志] 开始调用addUser方法,参数:" + username);
        
        try {
            // 调用真实对象的方法
            target.addUser(username);
            System.out.println("[日志] addUser方法执行成功");
        } catch (Exception e) {
            System.out.println("[日志] addUser方法执行异常:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("[日志] addUser方法执行结束,耗时:" + (System.currentTimeMillis() - start) + "ms");
        }
    }

    @Override
    public String getUser(String id) {
        long start = System.currentTimeMillis();
        System.out.println("[日志] 开始调用getUser方法,参数:" + id);
        
        try {
            String result = target.getUser(id);
            System.out.println("[日志] getUser方法执行成功,返回:" + result);
            return result;
        } catch (Exception e) {
            System.out.println("[日志] getUser方法执行异常:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("[日志] getUser方法执行结束,耗时:" + (System.currentTimeMillis() - start) + "ms");
        }
    }
}

// 客户端通过代理访问
public class StaticProxyDemo {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();          // 真实对象
        UserService proxy = new UserServiceProxy(target);    // 代理对象
        
        // 客户端面向UserService接口编程,完全感知不到代理的存在
        proxy.addUser("李四");
        System.out.println("-------------------");
        proxy.getUser("2001");
    }
}

静态代理成功地将日志、计时等增强逻辑从业务代码中剥离,但缺点也很明显:代理类必须手动实现接口的每一个方法,当接口方法很多或需要为多个接口创建代理时,会产生大量重复的样板代码。此外,如果增强逻辑发生变化,需要修改所有代理类。

2.3 动态代理演进

2.3.1 JDK动态代理

JDK动态代理利用Java反射机制在运行时动态生成代理类,无需手动编写代理类的Java文件。

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

// InvocationHandler实现:封装横切逻辑
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 {
        long start = System.currentTimeMillis();
        System.out.println("[JDK动态代理] 开始调用" + method.getName() + "方法,参数:" + arrayToString(args));
        
        try {
            // 通过反射调用真实对象的方法
            Object result = method.invoke(target, args);
            System.out.println("[JDK动态代理] " + method.getName() + "方法执行成功,返回:" + result);
            return result;
        } catch (Exception e) {
            System.out.println("[JDK动态代理] " + method.getName() + "方法执行异常:" + e.getCause());
            throw e.getCause();
        } finally {
            System.out.println("[JDK动态代理] " + method.getName() + "方法执行结束,耗时:" 
                + (System.currentTimeMillis() - start) + "ms");
        }
    }
    
    private String arrayToString(Object[] array) {
        return array == null ? "null" : java.util.Arrays.toString(array);
    }
}

public class JdkDynamicProxyDemo {
    public static void main(String[] args) {
        // 保存生成的代理类字节码到磁盘,方便分析
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        
        UserService target = new UserServiceImpl();
        
        // 创建代理对象:需要类加载器、接口数组、InvocationHandler
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new LogInvocationHandler(target)
        );
        
        proxy.addUser("王五");
        System.out.println("-------------------");
        proxy.getUser("3001");
    }
}

JDK动态代理的限制:从Proxy.newProxyInstance的参数可以看出,必须传入一组接口。这是因为JDK生成的代理类继承了java.lang.reflect.Proxy类(Java单继承限制),只能通过实现接口的方式来代理目标对象。如果目标类没有实现任何接口,则无法使用JDK动态代理。

2.3.2 CGLIB动态代理

CGLIB(Code Generation Library)通过继承目标类并重写其方法的方式生成代理子类,因此可以代理未实现接口的类。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 没有实现接口的普通类
class ProductService {
    public void addProduct(String name) {
        System.out.println("添加商品:" + name);
    }
    
    public String getProduct(String id) {
        System.out.println("查询商品:" + id);
        return "Product[id=" + id + ", name=iPhone]";
    }
}

// MethodInterceptor实现
class CglibLogInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("[CGLIB代理] 开始调用" + method.getName() + "方法");
        
        try {
            // 调用父类(即目标类)的方法,注意使用proxy.invokeSuper而非method.invoke
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("[CGLIB代理] " + method.getName() + "方法执行成功");
            return result;
        } catch (Exception e) {
            System.out.println("[CGLIB代理] " + method.getName() + "方法执行异常:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("[CGLIB代理] " + method.getName() + "方法执行结束,耗时:" 
                + (System.currentTimeMillis() - start) + "ms");
        }
    }
}

public class CglibProxyDemo {
    public static void main(String[] args) {
        // 创建Enhancer增强器
        Enhancer enhancer = new Enhancer();
        // 设置父类(目标类)
        enhancer.setSuperclass(ProductService.class);
        // 设置回调拦截器
        enhancer.setCallback(new CglibLogInterceptor());
        
        // 创建代理对象
        ProductService proxy = (ProductService) enhancer.create();
        
        proxy.addProduct("MacBook Pro");
        System.out.println("-------------------");
        proxy.getProduct("P001");
    }
}

CGLIB原理分析Enhancer在运行时动态生成目标类的子类字节码,并通过MethodInterceptor拦截所有方法调用。MethodProxy.invokeSuper()采用FastClass机制,避免了反射调用的性能损耗。但CGLIB也有局限性:final类和final方法无法被代理,因为子类无法重写final方法。

2.3.3 Javassist与ByteBuddy简介

框架特点优势劣势
Javassist通过直接操作字符串形式的Java代码生成字节码学习曲线平缓,适合简单动态类生成性能相对较低,复杂场景代码可读性差
ByteBuddy流式API构建字节码,类型安全高性能,API设计优雅,文档丰富学习成本较高,依赖库较大

ByteBuddy正逐渐成为Java字节码生成领域的首选,Hibernate 5+、Mockito等知名项目都已转向ByteBuddy。

2.4 代理模式的进阶特性

2.4.1 缓存代理(Cache Proxy)

interface DataService {
    String queryData(String key);
}

class DataServiceImpl implements DataService {
    @Override
    public String queryData(String key) {
        System.out.println("从数据库查询数据,key=" + key);
        // 模拟耗时操作
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        return "Data for " + key;
    }
}

class CacheProxy implements InvocationHandler {
    private Object target;
    private Map<String, Object> cache = new ConcurrentHashMap<>();
    
    public CacheProxy(Object target) { this.target = target; }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String key = (String) args[0];
        if (cache.containsKey(key)) {
            System.out.println("[缓存代理] 缓存命中,直接返回");
            return cache.get(key);
        }
        System.out.println("[缓存代理] 缓存未命中,调用真实对象");
        Object result = method.invoke(target, args);
        cache.put(key, result);
        return result;
    }
}

2.4.2 延迟加载代理(虚拟代理)

// 模拟一个初始化成本高昂的大对象
class ImageLoader {
    public ImageLoader() {
        System.out.println("ImageLoader初始化,加载大量资源...");
        try { Thread.sleep(2000); } catch (InterruptedException e) {}
    }
    
    public void display() {
        System.out.println("显示图片");
    }
}

class LazyImageProxy implements InvocationHandler {
    private Object target;
    private Class<?> targetClass;
    
    public LazyImageProxy(Class<?> targetClass) {
        this.targetClass = targetClass;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (target == null) {
            System.out.println("[虚拟代理] 首次调用,创建真实对象");
            target = targetClass.getDeclaredConstructor().newInstance();
        }
        return method.invoke(target, args);
    }
}

2.4.3 保护代理(Protection Proxy)

class ProtectionProxy implements InvocationHandler {
    private Object target;
    private Set<String> allowedRoles;
    
    public ProtectionProxy(Object target, Set<String> allowedRoles) {
        this.target = target;
        this.allowedRoles = allowedRoles;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String currentUserRole = getCurrentUserRole(); // 从上下文获取
        if (!allowedRoles.contains(currentUserRole)) {
            throw new SecurityException("当前用户无权限执行" + method.getName());
        }
        return method.invoke(target, args);
    }
    
    private String getCurrentUserRole() {
        return "USER"; // 模拟
    }
}

2.4.4 远程代理(Remote Proxy)

远程代理将在第四章分布式环境中详细展开。

2.5 动态代理调用时序图

sequenceDiagram
    participant Client as 客户端
    participant Proxy as 代理对象<br/>(Proxy实例)
    participant Handler as InvocationHandler<br/>(LogInvocationHandler)
    participant Real as 真实对象<br/>(RealSubject)
    
    Client->>Proxy: 1. 调用业务方法<br/>proxy.addUser("张三")
    Note over Proxy: 代理对象是运行时<br/>动态生成的类实例
    
    Proxy->>Handler: 2. invoke(proxy, method, args)
    Note over Handler: 进入统一拦截逻辑
    
    Handler->>Handler: 3. 前置增强<br/>记录日志、开始计时
    
    Handler->>Real: 4. method.invoke(target, args)
    Note over Real: 反射调用真实方法
    
    Real-->>Handler: 5. 返回执行结果
    
    Handler->>Handler: 6. 后置增强<br/>记录日志、计算耗时
    
    Handler-->>Proxy: 7. 返回结果
    
    Proxy-->>Client: 8. 返回最终结果

时序图解读:此图完整展示了JDK动态代理的方法调用链路。当客户端调用代理对象的业务方法时,调用首先进入由JVM动态生成的代理类($Proxy0)。这个代理类的每个接口方法实现中,都会调用父类Proxy持有的InvocationHandler的invoke方法。在invoke方法内部,开发者可以编写任意的增强逻辑,然后通过反射调用真实对象的方法。最终,结果原路返回。

这种设计实现了方法级别的统一拦截。无论客户端调用哪个业务方法,都会经过同一个InvocationHandler,从而避免了为每个方法编写重复的增强代码。Spring AOP正是基于这一机制,通过@Before、@After等注解实现了面向切面编程。


三、源码级应用分析

代理模式在Java生态中的应用之广泛超乎想象。理解这些框架是如何使用代理的,能极大提升我们的架构设计能力。

3.1 JDK中的代理应用

3.1.1 java.lang.reflect.Proxy与InvocationHandler

这是JDK动态代理的核心API。Proxy.newProxyInstance方法内部经历了以下步骤:

  1. 根据传入的接口数组生成代理类字节码
  2. 调用本地方法defineClass0将字节码加载到JVM
  3. 通过反射创建代理类实例,并将InvocationHandler传入构造函数

生成的代理类大致结构如下:

public final class $Proxy0 extends Proxy implements UserService {
    private static Method m3; // addUser方法
    private static Method m4; // getUser方法
    
    static {
        m3 = Class.forName("UserService").getMethod("addUser", String.class);
        m4 = Class.forName("UserService").getMethod("getUser", String.class);
    }
    
    public $Proxy0(InvocationHandler h) { super(h); }
    
    public final void addUser(String username) {
        super.h.invoke(this, m3, new Object[]{username});
    }
    
    public final String getUser(String id) {
        return (String) super.h.invoke(this, m4, new Object[]{id});
    }
}

3.1.2 java.rmi.Remote与UnicastRemoteObject

RMI(Remote Method Invocation)是Java原生的远程调用方案。当客户端通过Naming.lookup获取远程对象时,实际返回的是一个Stub代理对象。这个代理对象实现了远程接口,内部封装了与远程服务器通信的细节(序列化、网络传输等)。

3.1.3 java.sql.Connection中的数据库连接代理

当使用连接池(如HikariCP、Druid)获取数据库连接时,返回的Connection对象并非真正的物理连接,而是一个代理连接。以HikariCP为例:

  • ProxyConnection实现了Connection接口,持有真实的物理连接引用
  • 当调用close()方法时,代理对象并不会真正关闭物理连接,而是将连接归还给连接池
  • 这种设计使得连接复用成为可能,极大地提升了数据库访问性能

3.1.4 Collections.synchronizedList等同步包装器

List<String> list = Collections.synchronizedList(new ArrayList<>());

返回的SynchronizedList是List接口的保护代理,它在所有方法调用前加上了synchronized互斥锁,确保线程安全。这是一种典型的保护代理变体。

3.2 Spring框架深度剖析

3.2.1 Spring AOP代理机制

Spring AOP的核心类是DefaultAopProxyFactory,其createAopProxy方法决定了使用何种代理方式:

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    } else {
        return new JdkDynamicAopProxy(config);
    }
}

选择策略

  • 如果目标类实现了接口且未强制使用CGLIB,默认使用JdkDynamicAopProxy
  • 如果目标类未实现接口,或设置了proxyTargetClass=true,则使用CglibAopProxy

JdkDynamicAopProxy实现了InvocationHandler接口,在invoke方法中组织了拦截器链的调用。

3.2.2 @Transactional事务代理

@Transactional注解通过TransactionInterceptor实现,该拦截器是MethodInterceptor的子类。当Spring容器创建带有@Transactional的Bean时,会为其创建代理对象。调用代理方法时,拦截器链中会包含TransactionInterceptor,它负责:

  • 调用前:获取事务管理器,开启事务
  • 调用后:根据执行结果提交或回滚事务

3.2.3 @Async异步代理

@AsyncAsyncExecutionInterceptor处理,其invoke方法会判断方法返回值类型:

  • 返回Future:包装为ListenableFuture
  • 返回void:直接提交任务到线程池执行

同类方法调用失效的原因:当Service内部方法相互调用时(如A方法调用B方法),调用的是this对象的直接方法,绕过了Spring的代理对象,因此@Async增强失效。解决方案是通过AopContext.currentProxy()获取当前代理对象再调用。

3.2.4 @Cacheable缓存代理

CacheInterceptor实现了缓存切面逻辑。在执行目标方法前,它会根据key生成策略计算缓存键,查询缓存;若命中则直接返回缓存值(目标方法不会执行),否则执行目标方法并将结果存入缓存。

3.2.5 ScopedProxyMode作用域代理

当单例Bean依赖一个Session作用域Bean时,Spring会注入一个ScopedProxyFactoryBean生成的代理对象。这个代理对象会在每次方法调用时,从当前请求上下文中获取真实的Session Bean进行委派,从而解决作用域不匹配问题。

3.2.6 Spring Data JPA的Repository代理

Spring Data JPA为每个Repository接口生成代理实现。JdkDynamicAopProxy生成的对象实现了Repository接口,其内部通过SimpleJpaRepository(默认实现)完成CRUD操作,并支持通过方法名约定生成查询逻辑。

3.3 MyBatis框架

3.3.1 MapperProxy与MapperProxyFactory

MyBatis的Mapper接口完全没有实现类,其所有SQL执行都依赖于动态代理:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        // 从缓存中获取MapperMethod,它封装了SQL语句的详细信息
        MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
}

当调用Mapper接口方法时,MapperProxy根据方法名找到对应的MappedStatement,然后通过SqlSession执行SQL。

3.3.2 Plugin代理链

MyBatis插件机制基于责任链模式与动态代理的结合:

public class Plugin implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 检查当前方法是否需要被拦截
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
        }
        return method.invoke(target, args);
    }
    
    public static Object wrap(Object target, Interceptor interceptor) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new Plugin(target, interceptor, signatureMap)
        );
    }
}

多个插件会层层包装Executor、StatementHandler等核心对象,形成代理链。

3.3.3 ConnectionLogger

ConnectionLogger通过JDK动态代理包装Connection对象,实现SQL日志输出功能,是典型的日志代理应用。

3.4 Dubbo框架

3.4.1 服务消费者代理

Dubbo消费者端通过ProxyFactory生成远程服务的本地代理。以默认的JavassistProxyFactory为例:

public class JavassistProxyFactory extends AbstractProxyFactory {
    @Override
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }
}

生成的代理对象将方法调用封装为RpcInvocation,通过Invoker发送到远程服务提供者。

3.4.2 本地存根(Stub)与本地伪装(Mock)

Dubbo允许配置stubmock属性,当启用时,代理工厂会生成一个包含预处理/容错逻辑的代理类,实现对远程调用的保护增强。

3.5 其他框架

  • Feign:基于JDK动态代理将接口方法注解转换为HTTP请求,由ReflectiveFeign内部的FeignInvocationHandler处理。
  • Hibernate懒加载:使用CGLIB或ByteBuddy生成实体类的子类代理,重写getter方法,在首次访问时触发数据库加载。

四、分布式环境下的代理模式

当系统架构从单体演进到分布式,代理模式的价值被进一步放大。远程代理(Remote Proxy)成为屏蔽网络通信复杂性的关键手段。

4.1 RPC远程代理的本质

无论是Dubbo、gRPC还是Thrift,客户端持有的都是服务接口的代理对象。这个代理对象封装了:

  • 服务发现:从注册中心获取服务提供者列表
  • 负载均衡:选择合适的服务节点
  • 网络通信:建立连接、发送请求、接收响应
  • 序列化/反序列化:将Java对象转换为字节流及反向过程
  • 容错处理:超时重试、熔断降级

4.2 微服务网关中的反向代理

Spring Cloud Gateway基于Netty实现了一个反向代理服务器。它通过RoutePredicateHandlerMapping匹配路由规则,然后将请求代理转发到目标微服务。这里的代理是在HTTP协议层面的反向代理。

4.3 分布式缓存代理

在Redis Cluster客户端(如JedisCluster、Lettuce)中,客户端内部持有槽位与节点的映射关系,对用户透明的代理层负责将Key路由到正确的Redis节点。

4.4 数据库中间件代理

ShardingSphere-Proxy作为独立的数据库代理服务,接收应用端的SQL请求,然后根据分片规则将SQL改写并路由到真实的后端数据库,最后归并结果返回。对应用来说,它就像连接了一个单机数据库。

4.5 服务网格Sidecar代理

在Istio架构中,每个Pod内注入的Envoy Sidecar代理接管了所有进出流量。应用进程只需访问localhost,Envoy负责服务发现、负载均衡、熔断、遥测等职责。这是一种透明代理的极致体现。

4.6 简易RPC客户端示例

// RPC请求封装
class RpcRequest implements Serializable {
    private String className;
    private String methodName;
    private Class<?>[] parameterTypes;
    private Object[] arguments;
    // getters/setters...
}

// RPC代理处理器
class RpcInvocationHandler implements InvocationHandler {
    private String host;
    private int port;
    
    public RpcInvocationHandler(String host, int port) {
        this.host = host;
        this.port = port;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RpcRequest request = new RpcRequest();
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameterTypes(method.getParameterTypes());
        request.setArguments(args);
        
        // 通过Socket发送请求到远程服务器
        try (Socket socket = new Socket(host, port);
             ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
             ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
            
            out.writeObject(request);
            Object result = in.readObject();
            return result;
        }
    }
}

// 客户端获取代理
public class RpcClient {
    @SuppressWarnings("unchecked")
    public static <T> T getProxy(Class<T> interfaceClass, String host, int port) {
        return (T) Proxy.newProxyInstance(
            interfaceClass.getClassLoader(),
            new Class<?>[]{interfaceClass},
            new RpcInvocationHandler(host, port)
        );
    }
    
    public static void main(String[] args) {
        UserService userService = RpcClient.getProxy(UserService.class, "127.0.0.1", 8080);
        userService.addUser("远程用户");  // 实际通过网络执行
    }
}

4.7 RPC远程代理调用架构图

flowchart TB
    subgraph Client["客户端应用"]
        A[业务代码] --> B[Proxy<br/>代理对象]
    end
    
    subgraph RPC框架["RPC客户端框架"]
        C[InvocationHandler]
        D[序列化<br/>Protobuf/Hessian]
        E[网络客户端<br/>Netty/Socket]
    end
    
    subgraph Network["网络传输"]
        F[TCP/HTTP]
    end
    
    subgraph Server["远程服务器"]
        G[网络服务端]
        H[反序列化]
        I[反射调用]
        J[RealSubject<br/>真实服务]
    end
    
    B --> C
    C --> D
    D --> E
    E --> F
    F --> G
    G --> H
    H --> I
    I --> J
    J -.-> I
    I -.-> H
    H -.-> G
    G -.-> F
    F -.-> E
    E -.-> D
    D -.-> C
    C -.-> B

架构图解读:该图展示了一个典型的RPC框架调用链路。客户端业务代码调用的Proxy对象是由动态代理生成的,其InvocationHandler负责拦截所有方法调用。在InvocationHandler中,方法调用的元数据(类名、方法名、参数等)被封装为RpcRequest对象,然后通过序列化组件转换为二进制流。网络客户端将二进制流通过TCP/HTTP协议发送到远程服务器。服务端接收到数据后,经过反序列化还原为请求对象,再通过Java反射调用真实的业务服务方法。执行结果沿原路返回,最终到达客户端业务代码。

整个过程中,客户端开发人员完全感觉不到网络通信的存在,就像调用本地方法一样。这正是代理模式在分布式系统中的巨大价值——位置透明性。高级的RPC框架还会在代理层加入服务发现、负载均衡、熔断降级等复杂特性,进一步提升系统的可用性和扩展性。


五、对比辨析

5.1 代理模式 vs 装饰器模式

维度代理模式装饰器模式
意图控制对对象的访问(权限、延迟加载、远程等)动态地为对象添加新功能
关注点访问控制、生命周期管理功能增强与组合
关系建立代理通常在编译期或运行时由工厂/框架创建装饰器由客户端显式创建并嵌套
实例Spring AOP代理、Hibernate懒加载代理Java I/O流(BufferedReader装饰Reader)

虽然两者在类结构上几乎完全相同,但区分它们的钥匙在于设计意图。代理关心“能不能调用”,装饰器关心“调用后还能多做些什么”。

5.2 代理模式 vs 适配器模式

适配器模式改变接口的形态,使不兼容的接口可以协同工作;代理模式则保持接口不变,只是在调用前后增加控制逻辑。

5.3 代理模式 vs 门面模式

门面模式为一组复杂的子系统提供统一的简单接口,是一对多的关系;代理模式通常代理单一对象,是一对一的关系。

5.4 静态代理 vs 动态代理

静态代理在编译期确定代理类,每个代理类只服务于一个接口;动态代理在运行时生成,一个InvocationHandler可以处理多个接口多个方法。

5.5 JDK动态代理 vs CGLIB代理

对比项JDK动态代理CGLIB代理
原理实现接口继承目标类
限制目标必须实现接口不能代理final类和方法
性能反射调用,稍慢FastClass机制,较快
默认策略Spring AOP默认未实现接口时的选择

5.6 代理模式 vs AOP

AOP是代理模式在横切关注点领域的泛化实现。代理模式是AOP的底层技术基石,AOP通过定义切点(Pointcut)和通知(Advice)将增强逻辑从代理类中进一步解耦,使得横切逻辑可以模块化管理。


六、适用场景深度剖析

场景一:声明式事务管理

完整可运行Demo

// 模拟事务管理器
class TransactionManager {
    public void begin() { System.out.println("[事务] 开启事务"); }
    public void commit() { System.out.println("[事务] 提交事务"); }
    public void rollback() { System.out.println("[事务] 回滚事务"); }
}

// 业务接口
interface AccountService {
    void transfer(String from, String to, double amount);
}

// 业务实现
class AccountServiceImpl implements AccountService {
    @Override
    public void transfer(String from, String to, double amount) {
        System.out.println("执行转账: " + from + " -> " + to + ", 金额: " + amount);
        if (amount > 10000) throw new RuntimeException("转账金额超限");
    }
}

// 事务代理InvocationHandler
class TransactionProxy implements InvocationHandler {
    private Object target;
    private TransactionManager txManager;
    
    public TransactionProxy(Object target, TransactionManager txManager) {
        this.target = target;
        this.txManager = txManager;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        txManager.begin();
        try {
            Object result = method.invoke(target, args);
            txManager.commit();
            return result;
        } catch (Exception e) {
            txManager.rollback();
            throw e;
        }
    }
}

public class TransactionDemo {
    public static void main(String[] args) {
        AccountService target = new AccountServiceImpl();
        TransactionManager txManager = new TransactionManager();
        
        AccountService proxy = (AccountService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new TransactionProxy(target, txManager)
        );
        
        proxy.transfer("Alice", "Bob", 5000);  // 正常提交
        System.out.println("---");
        try {
            proxy.transfer("Alice", "Bob", 20000); // 触发回滚
        } catch (Exception e) {
            System.out.println("捕获异常: " + e.getMessage());
        }
    }
}

Mermaid类图

classDiagram
    class AccountService {
        <<interface>>
        +transfer(from, to, amount) void
    }
    
    class AccountServiceImpl {
        +transfer(from, to, amount) void
    }
    
    class TransactionProxy {
        -target: Object
        -txManager: TransactionManager
        +invoke(proxy, method, args) Object
    }
    
    class TransactionManager {
        +begin() void
        +commit() void
        +rollback() void
    }
    
    AccountService <|.. AccountServiceImpl
    AccountService <|.. Proxy
    TransactionProxy ..> TransactionManager : 使用
    TransactionProxy --> AccountService : 代理

文字说明

声明式事务是代理模式在Spring框架中的经典应用。通过动态代理,我们将事务管理的横切逻辑从业务代码中完全剥离。当客户端调用代理对象的transfer方法时,调用首先进入TransactionProxy的invoke方法。在这里,事务管理器开启事务,然后通过反射调用真实的业务逻辑。如果业务方法正常返回,事务提交;如果抛出异常,事务回滚。

这种设计使得业务开发人员无需关心事务边界,只需专注于业务逻辑的编写。Spring的@Transactional注解正是基于同样的原理,通过AOP代理在运行时织入事务处理逻辑。理解这一点,有助于深入掌握Spring事务的传播行为、失效场景等高级特性。

场景二:缓存代理加速计算

完整可运行Demo

interface ExpensiveCalculator {
    int compute(int input);
}

class RealCalculator implements ExpensiveCalculator {
    @Override
    public int compute(int input) {
        System.out.println("[真实计算] 正在为 " + input + " 执行耗时计算...");
        try { Thread.sleep(2000); } catch (InterruptedException e) {}
        return input * input; // 模拟复杂运算
    }
}

class CacheProxy implements InvocationHandler {
    private Object target;
    private Map<Integer, Integer> cache = new ConcurrentHashMap<>();
    
    public CacheProxy(Object target) { this.target = target; }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        int input = (Integer) args[0];
        if (cache.containsKey(input)) {
            System.out.println("[缓存代理] 键 " + input + " 命中缓存,直接返回 " + cache.get(input));
            return cache.get(input);
        }
        System.out.println("[缓存代理] 键 " + input + " 未命中,调用真实对象");
        Integer result = (Integer) method.invoke(target, args);
        cache.put(input, result);
        return result;
    }
}

public class CacheProxyDemo {
    public static void main(String[] args) {
        ExpensiveCalculator target = new RealCalculator();
        ExpensiveCalculator proxy = (ExpensiveCalculator) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new CacheProxy(target)
        );
        
        System.out.println("第一次调用 compute(5): " + proxy.compute(5));
        System.out.println("第二次调用 compute(5): " + proxy.compute(5));
        System.out.println("第一次调用 compute(7): " + proxy.compute(7));
    }
}

Mermaid时序图

sequenceDiagram
    participant Client
    participant Proxy as CacheProxy
    participant Cache as 缓存存储<br/>(ConcurrentHashMap)
    participant Real as RealCalculator
    
    Client->>Proxy: 1. compute(5)
    Proxy->>Cache: 2. 检查键5是否存在
    Cache-->>Proxy: 3. 未命中
    
    Proxy->>Real: 4. compute(5)
    Real-->>Proxy: 5. 返回25
    
    Proxy->>Cache: 6. 存入(5, 25)
    Proxy-->>Client: 7. 返回25
    
    Client->>Proxy: 8. 再次compute(5)
    Proxy->>Cache: 9. 检查键5是否存在
    Cache-->>Proxy: 10. 命中,返回25
    Proxy-->>Client: 11. 直接返回25

文字说明

缓存代理通过拦截方法调用,将计算密集或IO密集操作的返回结果缓存起来,避免重复执行相同计算。时序图清晰地展示了两次调用compute(5)的不同路径:第一次调用时缓存未命中,代理转发给真实计算对象,并将结果存入缓存;第二次调用时缓存命中,代理直接从缓存返回结果,完全绕过了真实对象。

在实际生产环境中,缓存代理需要额外考虑缓存过期策略(TTL)、缓存击穿防护(使用锁或原子操作保证只有一个线程计算并填充缓存)、缓存雪崩预防(设置随机过期时间)等复杂问题。Spring Cache抽象(@Cacheable)和分布式缓存(Redis)的结合,正是这一模式的工业化实现。

场景三:权限访问控制

完整可运行Demo

interface DocumentService {
    String readDocument(String docId);
    void deleteDocument(String docId);
}

class DocumentServiceImpl implements DocumentService {
    @Override
    public String readDocument(String docId) {
        return "文档内容: " + docId;
    }
    
    @Override
    public void deleteDocument(String docId) {
        System.out.println("删除文档: " + docId);
    }
}

class ProtectionProxy implements InvocationHandler {
    private Object target;
    private String currentUserRole;
    private Set<String> deleteAllowedRoles = Set.of("ADMIN", "MANAGER");
    
    public ProtectionProxy(Object target, String currentUserRole) {
        this.target = target;
        this.currentUserRole = currentUserRole;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("deleteDocument".equals(method.getName())) {
            if (!deleteAllowedRoles.contains(currentUserRole)) {
                throw new SecurityException("用户角色 " + currentUserRole + " 无权删除文档");
            }
        }
        return method.invoke(target, args);
    }
}

public class ProtectionProxyDemo {
    public static void main(String[] args) {
        DocumentService target = new DocumentServiceImpl();
        
        // 普通用户
        DocumentService userProxy = (DocumentService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new ProtectionProxy(target, "USER")
        );
        
        System.out.println(userProxy.readDocument("doc1"));
        try {
            userProxy.deleteDocument("doc1");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        // 管理员
        DocumentService adminProxy = (DocumentService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new ProtectionProxy(target, "ADMIN")
        );
        adminProxy.deleteDocument("doc1");
    }
}

Mermaid流程图

flowchart TD
    Start([客户端调用代理方法]) --> CheckMethod{方法是否为<br/>deleteDocument?}
    CheckMethod -->|否| InvokeReal[调用真实对象方法]
    CheckMethod -->|是| GetRole[获取当前用户角色]
    
    GetRole --> CheckPerm{角色是否在<br/>允许删除列表中?}
    CheckPerm -->|是| InvokeReal
    CheckPerm -->|否| ThrowEx[抛出SecurityException]
    
    InvokeReal --> ReturnResult[返回结果]
    ThrowEx --> End([结束])
    ReturnResult --> End

文字说明

保护代理的核心职责是在调用目标方法之前执行访问控制检查。流程图清晰地展示了决策分支:对于非敏感方法(如readDocument),代理直接放行;对于敏感方法(如deleteDocument),代理会获取当前用户的角色信息,并与预定义的白名单进行比对,只有匹配时才允许调用。

这种模式在Spring Security的方法级安全中得到广泛应用。@PreAuthorize("hasRole('ADMIN')")注解的背后,就是通过AOP代理在方法调用前执行权限表达式计算。与直接在业务代码中编写if-else权限判断相比,代理模式实现了权限逻辑与业务逻辑的完全解耦,且支持统一的权限策略管理。

场景四:远程服务调用(RPC)

完整可运行Demo

完整RPC示例涉及服务端和客户端两部分。由于篇幅限制,这里展示客户端的核心代理逻辑(服务端实现反射调用过程省略,详见4.6节)。

interface OrderService {
    String createOrder(String productId, int quantity);
}

// 客户端RPC代理
class RpcClientProxy implements InvocationHandler {
    private String host;
    private int port;
    
    public RpcClientProxy(String host, int port) {
        this.host = host;
        this.port = port;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 封装请求对象
        RpcRequest request = new RpcRequest();
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameterTypes(method.getParameterTypes());
        request.setArguments(args);
        
        System.out.println("[RPC代理] 准备远程调用: " + method.getName());
        
        // 2. 网络传输
        try (Socket socket = new Socket(host, port);
             ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
             ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
            
            out.writeObject(request);
            out.flush();
            
            // 3. 接收结果
            Object result = in.readObject();
            System.out.println("[RPC代理] 收到远程响应: " + result);
            return result;
        }
    }
}

public class RpcProxyDemo {
    @SuppressWarnings("unchecked")
    public static <T> T refer(Class<T> interfaceClass, String host, int port) {
        return (T) Proxy.newProxyInstance(
            interfaceClass.getClassLoader(),
            new Class<?>[]{interfaceClass},
            new RpcClientProxy(host, port)
        );
    }
    
    public static void main(String[] args) {
        OrderService orderService = refer(OrderService.class, "127.0.0.1", 9999);
        String result = orderService.createOrder("P1001", 2);
        System.out.println("客户端获得结果: " + result);
    }
}

Mermaid时序图

sequenceDiagram
    participant Client
    participant Proxy as RPC代理<br/>(RpcClientProxy)
    participant Serializer as 序列化器
    participant Socket as 网络Socket
    participant Server as 远程服务端
    participant Real as 真实服务<br/>(OrderServiceImpl)
    
    Client->>Proxy: 1. createOrder("P1001", 2)
    Proxy->>Proxy: 2. 构建RpcRequest
    Proxy->>Serializer: 3. 序列化请求对象
    Serializer-->>Proxy: 4. 字节流
    
    Proxy->>Socket: 5. 发送网络请求
    Socket->>Server: 6. TCP传输
    
    Server->>Server: 7. 反序列化请求
    Server->>Real: 8. 反射调用真实方法
    Real-->>Server: 9. 返回执行结果
    Server->>Server: 10. 序列化响应
    
    Server-->>Socket: 11. 返回响应数据
    Socket-->>Proxy: 12. 接收响应
    
    Proxy->>Proxy: 13. 反序列化结果
    Proxy-->>Client: 14. 返回结果

文字说明

RPC代理是代理模式在分布式系统中最为重要的应用形式。时序图详细描绘了一次远程调用的完整生命周期。客户端通过动态代理获得了OrderService接口的一个代理实例,当调用createOrder方法时,调用被RpcClientProxy拦截。代理对象并不执行任何本地业务逻辑,而是将方法调用信息封装为RpcRequest对象,经过序列化后通过网络发送给远端服务器。

服务端接收到请求后,根据请求中的类名和方法名找到对应的服务实例和方法,通过反射执行调用,并将结果序列化后返回。整个过程中,客户端代码完全没有感知到网络通信的存在,这种透明性极大地简化了分布式系统的开发复杂度。Dubbo的消费者端代理就是这一模式的工业级实现,其在基础之上还集成了服务发现、负载均衡、集群容错等高级特性。

场景五:延迟加载代理

完整可运行Demo

// 模拟一个资源密集的大对象
class HighResolutionImage {
    private String fileName;
    
    public HighResolutionImage(String fileName) {
        this.fileName = fileName;
        System.out.println("[系统] 正在从磁盘加载高分辨率图片: " + fileName);
        try { Thread.sleep(3000); } catch (InterruptedException e) {}
        System.out.println("[系统] 图片加载完成: " + fileName);
    }
    
    public void display() {
        System.out.println("[显示] 正在渲染图片: " + fileName);
    }
}

// 图片接口
interface Image {
    void display();
}

// 真实图片类(实现Image接口)
class RealImage implements Image {
    private HighResolutionImage delegate;
    private String fileName;
    
    public RealImage(String fileName) {
        this.fileName = fileName;
    }
    
    @Override
    public void display() {
        if (delegate == null) {
            delegate = new HighResolutionImage(fileName);
        }
        delegate.display();
    }
}

// 虚拟代理(延迟加载)
class VirtualImageProxy implements Image {
    private String fileName;
    private RealImage realImage;
    
    public VirtualImageProxy(String fileName) {
        this.fileName = fileName;
    }
    
    @Override
    public void display() {
        if (realImage == null) {
            System.out.println("[虚拟代理] 首次调用display,触发真实图片加载");
            realImage = new RealImage(fileName);
        } else {
            System.out.println("[虚拟代理] 图片已加载,直接使用");
        }
        realImage.display();
    }
}

public class VirtualProxyDemo {
    public static void main(String[] args) {
        // 创建代理对象时并不加载真实图片
        Image image1 = new VirtualImageProxy("photo1.jpg");
        Image image2 = new VirtualImageProxy("photo2.jpg");
        System.out.println("代理对象创建完成,真实图片尚未加载");
        
        // 只有真正需要显示时才加载
        image1.display();
        System.out.println("---");
        image1.display(); // 第二次调用不会重新加载
        System.out.println("---");
        image2.display();
    }
}

Mermaid流程图

flowchart TD
    Start([客户端调用 image.display]) --> Proxy{代理对象中<br/>realImage是否已创建?}
    
    Proxy -->|否| Create[创建RealImage对象]
    Create --> Load[RealImage构造函数中<br/>加载高分辨率图片资源]
    Load --> Delegate1[调用realImage.display]
    
    Proxy -->|是| Skip[跳过创建步骤]
    Skip --> Delegate2[直接调用realImage.display]
    
    Delegate1 --> Render[渲染图片]
    Delegate2 --> Render
    Render --> End([结束])

文字说明

虚拟代理(延迟加载代理)的核心价值在于推迟高成本对象的创建时机,直到真正需要其服务时才进行初始化。在上例中,VirtualImageProxy在创建时只保存了文件名,并没有加载真实的图片数据。当客户端第一次调用display()方法时,代理检测到realImage为null,才触发RealImage的创建,进而加载高分辨率图片。后续的display()调用则直接使用已加载的对象。

这种模式在提升应用启动性能和降低内存占用方面效果显著。Hibernate的懒加载机制正是基于同样的原理:当从数据库查询一个实体时,其关联的集合属性默认返回的是CGLIB生成的代理对象,只有在代码真正访问该集合时,代理才会触发额外的SQL查询加载数据。合理使用虚拟代理可以有效避免N+1查询问题和无谓的数据库开销。


七、面试题精选与专家级解答

1. 静态代理、JDK动态代理、CGLIB代理有什么区别?各自的适用场景是什么?

参考答案

代理类型实现方式优点缺点适用场景
静态代理手动编写代理类实现接口简单直观,可读性强代码重复,维护困难接口稳定且数量少的场景
JDK动态代理运行时生成实现接口的代理类无需手动编码,灵活目标必须实现接口面向接口编程的通用场景
CGLIB代理运行时生成目标类的子类可代理未实现接口的类无法代理final类/方法需要代理普通类的场景

2. JDK动态代理为什么必须基于接口?内部是如何生成代理类的?

参考答案

JDK动态代理生成的代理类继承了java.lang.reflect.Proxy类。由于Java是单继承,代理类已经继承了Proxy,无法再继承目标类,因此只能通过实现接口的方式对目标对象进行代理。

生成过程:

  1. Proxy.newProxyInstance调用ProxyGenerator.generateProxyClass生成字节码
  2. 字节码中包含所有接口方法的实现,每个方法内部调用InvocationHandler.invoke
  3. 通过Unsafe.defineClassdefineClass0本地方法将字节码加载为Class对象
  4. 通过反射获取带有InvocationHandler参数的构造函数并创建实例

3. CGLIB通过继承实现代理,final方法和final类会被怎样处理?

参考答案

  • final类:CGLIB无法创建其子类,会抛出IllegalArgumentException
  • final方法:可以代理该类,但final方法无法被子类重写,因此对该方法的调用不会被拦截增强,直接执行父类(目标类)的实现。

Spring AOP在使用CGLIB时遇到final方法会给出警告日志,但不会阻止应用启动。

4. Spring AOP在什么情况下使用JDK动态代理,什么情况下使用CGLIB?如何强制使用CGLIB?

参考答案

  • 默认策略:如果目标类实现了至少一个接口,则使用JDK动态代理;否则使用CGLIB。
  • 强制使用CGLIB
    • 配置@EnableAspectJAutoProxy(proxyTargetClass = true)
    • 或通过XML配置<aop:config proxy-target-class="true">
    • 使用@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)

Spring Boot 2.x中,默认proxyTargetClass已改为true(通过AopAutoConfiguration配置),多数场景下优先使用CGLIB。

5. 代理模式和装饰器模式在代码结构上几乎相同,如何从意图上区分?

参考答案

判断标准是观察谁创建了关系以及创建的目的

  • 代理模式:代理对象通常由工厂方法或框架创建,客户端不知道(也不关心)自己拿到的是代理还是真实对象。代理的存在是为了控制访问(权限、事务、延迟等)。
  • 装饰器模式:装饰器由客户端显式创建并组合,客户端清楚地知道自己在为对象添加功能。装饰器的目的是增强功能,且支持多重装饰。

例如:BufferedReader装饰Reader是客户端主动添加缓冲功能;Spring的@Transactional代理是容器自动创建的,客户端并不感知。

6. MyBatis的Mapper接口没有实现类,它是如何通过代理执行SQL的?

参考答案

MyBatis通过MapperProxyFactory为每个Mapper接口创建MapperProxy代理对象。当调用Mapper接口方法时,执行流程如下:

  1. MapperProxy.invoke拦截方法调用
  2. Configuration中根据"接口全限定名.方法名"获取MappedStatement
  3. 将方法参数转换为SQL参数
  4. 调用SqlSession的对应方法(selectOne、insert等)执行数据库操作
  5. 将结果集映射为方法返回类型并返回

Mapper接口方法的实现实际上是在XML或注解中定义的SQL语句,代理对象负责将方法调用翻译为SQL执行。

7. Spring中@Async注解是如何通过代理实现异步执行的?为什么同类方法调用会导致@Async失效?

参考答案

实现原理

  1. Spring为标注了@Async的Bean创建代理对象
  2. 代理对象的invoke方法中,AsyncExecutionInterceptor拦截调用
  3. 拦截器将方法调用封装为CallableRunnable任务,提交给TaskExecutor线程池执行
  4. 立即返回Future或null(取决于方法返回值)

失效原因: 同类内部方法调用(如A方法调用this.B方法)时,调用的是目标对象自身的实例方法,绕过了Spring容器创建的代理对象,因此AsyncExecutionInterceptor无法拦截,异步自然失效。

解决方案

  • 通过@Autowired注入自身代理(循环依赖需注意)
  • 使用AopContext.currentProxy()获取当前代理对象再调用
  • 将异步方法拆分到另一个Bean中

8. Dubbo消费端生成的代理对象内部做了哪些事情?请简述调用链路。

参考答案

Dubbo消费端代理(由ProxyFactory生成)内部封装了以下职责:

  1. 服务发现:从注册中心获取服务提供者地址列表
  2. 路由与负载均衡:根据路由规则选择目标Provider
  3. 协议编解码:将方法调用转换为Dubbo协议格式的请求
  4. 网络通信:通过Netty发送请求并等待响应
  5. 结果处理:将响应反序列化为返回值或异常

调用链路

Proxy.method() 
  → InvokerInvocationHandler.invoke() 
  → MockClusterInvoker.invoke() 
  → FailoverClusterInvoker.doInvoke() 
  → 负载均衡选择Invoker 
  → DubboInvoker.doInvoke() 
  → ExchangeClient.request() 
  → NettyChannel.send()

9. 如何解决JDK动态代理性能低于直接调用的问题?有哪些优化手段?

参考答案

JDK动态代理性能开销主要来自反射调用。优化手段包括:

  1. 预热与缓存:将Method对象缓存,避免重复查找(JDK代理内部已做此优化)。
  2. Lambda表达式:Java 8+中可使用Lambda替代反射创建代理。
  3. 字节码增强替代反射:如CGLIB的FastClass机制,将反射调用替换为索引访问。
  4. JIT编译优化:JVM会内联频繁调用的反射代码,接近直接调用性能。
  5. 避免过度代理:仅对需要增强的方法进行代理,非切入点方法直接委派。

实测显示,在JIT充分预热后,JDK动态代理性能损失通常在10%-20%以内,对绝大多数业务场景可接受。

10. 代理模式与AOP的关系是什么?为什么说AOP是代理模式的升华?

参考答案

代理模式是AOP的技术实现基石,而AOP是代理模式在横切关注点领域的系统化升华

维度代理模式AOP
抽象层次设计模式级别编程范式级别
关注点单个对象的访问控制多个对象共享的横切逻辑
织入方式编码实现代理逻辑配置切点+通知,框架自动织入
灵活性代理逻辑与目标类强关联切面与目标类解耦,可复用

AOP通过切点表达式精确定位需要增强的连接点,通过通知类型(Before、After、Around等)灵活控制增强时机,通过切面将散落的横切逻辑模块化。这些高级抽象都是建立在代理模式的基础之上,使得开发者能够以声明式的方式管理横切关注点。


八、总结

本文从代理模式的基本定义出发,通过静态代理、JDK动态代理、CGLIB代理的完整代码演进,深入剖析了Spring、MyBatis、Dubbo等主流框架中的代理应用,并扩展到分布式环境下的远程代理场景。通过六个典型场景的独立Demo与图表分析,相信读者已对代理模式有了全面而深入的理解。代理模式是Java开发者进阶必备的设计模式知识,也是理解众多框架底层原理的关键钥匙。