概述
代理模式(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方法内部经历了以下步骤:
- 根据传入的接口数组生成代理类字节码
- 调用本地方法
defineClass0将字节码加载到JVM - 通过反射创建代理类实例,并将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异步代理
@Async由AsyncExecutionInterceptor处理,其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允许配置stub和mock属性,当启用时,代理工厂会生成一个包含预处理/容错逻辑的代理类,实现对远程调用的保护增强。
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,无法再继承目标类,因此只能通过实现接口的方式对目标对象进行代理。
生成过程:
Proxy.newProxyInstance调用ProxyGenerator.generateProxyClass生成字节码- 字节码中包含所有接口方法的实现,每个方法内部调用
InvocationHandler.invoke - 通过
Unsafe.defineClass或defineClass0本地方法将字节码加载为Class对象 - 通过反射获取带有
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接口方法时,执行流程如下:
MapperProxy.invoke拦截方法调用- 从
Configuration中根据"接口全限定名.方法名"获取MappedStatement - 将方法参数转换为SQL参数
- 调用
SqlSession的对应方法(selectOne、insert等)执行数据库操作 - 将结果集映射为方法返回类型并返回
Mapper接口方法的实现实际上是在XML或注解中定义的SQL语句,代理对象负责将方法调用翻译为SQL执行。
7. Spring中@Async注解是如何通过代理实现异步执行的?为什么同类方法调用会导致@Async失效?
参考答案:
实现原理:
- Spring为标注了@Async的Bean创建代理对象
- 代理对象的
invoke方法中,AsyncExecutionInterceptor拦截调用 - 拦截器将方法调用封装为
Callable或Runnable任务,提交给TaskExecutor线程池执行 - 立即返回Future或null(取决于方法返回值)
失效原因:
同类内部方法调用(如A方法调用this.B方法)时,调用的是目标对象自身的实例方法,绕过了Spring容器创建的代理对象,因此AsyncExecutionInterceptor无法拦截,异步自然失效。
解决方案:
- 通过
@Autowired注入自身代理(循环依赖需注意) - 使用
AopContext.currentProxy()获取当前代理对象再调用 - 将异步方法拆分到另一个Bean中
8. Dubbo消费端生成的代理对象内部做了哪些事情?请简述调用链路。
参考答案:
Dubbo消费端代理(由ProxyFactory生成)内部封装了以下职责:
- 服务发现:从注册中心获取服务提供者地址列表
- 路由与负载均衡:根据路由规则选择目标Provider
- 协议编解码:将方法调用转换为Dubbo协议格式的请求
- 网络通信:通过Netty发送请求并等待响应
- 结果处理:将响应反序列化为返回值或异常
调用链路:
Proxy.method()
→ InvokerInvocationHandler.invoke()
→ MockClusterInvoker.invoke()
→ FailoverClusterInvoker.doInvoke()
→ 负载均衡选择Invoker
→ DubboInvoker.doInvoke()
→ ExchangeClient.request()
→ NettyChannel.send()
9. 如何解决JDK动态代理性能低于直接调用的问题?有哪些优化手段?
参考答案:
JDK动态代理性能开销主要来自反射调用。优化手段包括:
- 预热与缓存:将Method对象缓存,避免重复查找(JDK代理内部已做此优化)。
- Lambda表达式:Java 8+中可使用Lambda替代反射创建代理。
- 字节码增强替代反射:如CGLIB的FastClass机制,将反射调用替换为索引访问。
- JIT编译优化:JVM会内联频繁调用的反射代码,接近直接调用性能。
- 避免过度代理:仅对需要增强的方法进行代理,非切入点方法直接委派。
实测显示,在JIT充分预热后,JDK动态代理性能损失通常在10%-20%以内,对绝大多数业务场景可接受。
10. 代理模式与AOP的关系是什么?为什么说AOP是代理模式的升华?
参考答案:
代理模式是AOP的技术实现基石,而AOP是代理模式在横切关注点领域的系统化升华。
| 维度 | 代理模式 | AOP |
|---|---|---|
| 抽象层次 | 设计模式级别 | 编程范式级别 |
| 关注点 | 单个对象的访问控制 | 多个对象共享的横切逻辑 |
| 织入方式 | 编码实现代理逻辑 | 配置切点+通知,框架自动织入 |
| 灵活性 | 代理逻辑与目标类强关联 | 切面与目标类解耦,可复用 |
AOP通过切点表达式精确定位需要增强的连接点,通过通知类型(Before、After、Around等)灵活控制增强时机,通过切面将散落的横切逻辑模块化。这些高级抽象都是建立在代理模式的基础之上,使得开发者能够以声明式的方式管理横切关注点。
八、总结
本文从代理模式的基本定义出发,通过静态代理、JDK动态代理、CGLIB代理的完整代码演进,深入剖析了Spring、MyBatis、Dubbo等主流框架中的代理应用,并扩展到分布式环境下的远程代理场景。通过六个典型场景的独立Demo与图表分析,相信读者已对代理模式有了全面而深入的理解。代理模式是Java开发者进阶必备的设计模式知识,也是理解众多框架底层原理的关键钥匙。