1. 代理模式核心价值解析
代理模式(Proxy Pattern)是结构型设计模式的典型代表,它通过引入代理对象来控制对原始对象的访问。这种模式的核心价值在于实现了"开闭原则"的优雅实践——在不修改原有代码的前提下扩展功能。就像明星与经纪人的关系,经纪人作为代理可以处理签约、档期等事务,而明星只需专注于表演本身。
在实际开发中,代理模式常用于以下场景:
- 远程代理(Remote Proxy):为位于不同地址空间的对象提供本地代表,例如RPC调用
- 虚拟代理(Virtual Proxy):延迟创建开销大的对象,如图片懒加载
- 保护代理(Protection Proxy):控制对原始对象的访问权限
- 智能引用(Smart Reference):在访问对象时执行额外操作,如引用计数
关键理解:代理模式本质上是"访问控制"而非"功能增强",所有功能扩展都应围绕访问行为本身展开
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 静态代理实现详解
2.1 基础实现模板
静态代理是最直观的实现方式,需要显式创建代理类。以下是一个标准的Java静态代理实现:
java复制// 抽象主题
interface Subject {
void request();
}
// 真实主题
class RealSubject implements Subject {
public void request() {
System.out.println("RealSubject handling request");
}
}
// 代理类
class Proxy implements Subject {
private RealSubject realSubject;
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.request();
postRequest();
}
private void preRequest() {
System.out.println("Proxy preprocessing");
}
private void postRequest() {
System.out.println("Proxy postprocessing");
}
}
2.2 静态代理的优缺点
优势:
- 代码结构清晰,符合单一职责原则
- 代理类和目标类分离,便于扩展
- 编译期即可确定代理关系,安全性高
局限性:
- 每个真实主题都需要创建对应的代理类
- 接口变更时需要同步修改代理类
- 代理功能无法动态变化
实际经验:在小型固定功能扩展场景下,静态代理是最高效的选择。但当系统存在大量需要代理的类时,应考虑动态代理方案。
3. 动态代理技术剖析
3.1 JDK动态代理实现
Java标准库提供了基于接口的动态代理支持,核心是java.lang.reflect.Proxy类:
java复制// 调用处理器
class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method " + method.getName());
return result;
}
}
// 使用示例
Subject realSubject = new RealSubject();
Subject proxyInstance = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(),
new Class[]{Subject.class},
new DynamicProxyHandler(realSubject)
);
proxyInstance.request();
3.2 CGLIB字节码增强
对于没有接口的类,可以使用CGLIB库实现动态代理:
java复制class RealService {
public void execute() {
System.out.println("RealService execution");
}
}
class CglibProxy implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method " + method.getName());
return result;
}
}
// 使用示例
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealService.class);
enhancer.setCallback(new CglibProxy());
RealService proxy = (RealService) enhancer.create();
proxy.execute();
3.3 动态代理对比分析
| 特性 | JDK动态代理 | CGLIB |
|---|---|---|
| 依赖 | Java标准库 | 第三方库 |
| 目标要求 | 必须实现接口 | 可代理普通类 |
| 性能 | 调用稍慢 | 生成慢但调用快 |
| 生成方式 | 反射 | 字节码增强 |
| 方法限制 | 只能代理接口方法 | 不能代理final方法 |
性能提示:在Spring AOP中,默认对接口使用JDK动态代理,对类使用CGLIB。可以通过
spring.aop.proxy-target-class=true强制使用CGLIB。
4. 代理模式高级应用
4.1 延迟初始化代理
虚拟代理的典型应用,实现资源的按需加载:
java复制class HeavyResourceProxy {
private HeavyResource realResource;
public void operation() {
if (realResource == null) {
synchronized (this) {
if (realResource == null) {
realResource = new HeavyResource(); // 昂贵的初始化
}
}
}
realResource.operation();
}
}
4.2 访问控制代理
实现精细化的权限管理:
java复制class AccessControlProxy implements SensitiveService {
private SensitiveService realService;
private User user;
public void sensitiveOperation() {
if (user.hasPermission("sensitive_op")) {
realService.sensitiveOperation();
} else {
throw new SecurityException("Permission denied");
}
}
}
4.3 日志记录代理
统一添加调用日志而不污染业务代码:
java复制class LoggingProxy implements InvocationHandler {
private Object target;
private Logger logger = LoggerFactory.getLogger(getClass());
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
logger.info("Invoking {}.{}", target.getClass().getSimpleName(), method.getName());
long start = System.nanoTime();
Object result = method.invoke(target, args);
long duration = (System.nanoTime() - start) / 1_000_000;
logger.info("Method executed in {} ms", duration);
return result;
}
}
5. 常见问题与解决方案
5.1 代理对象身份识别
问题场景: 当调用instanceof或获取类名时,代理对象会暴露代理身份
解决方案:
java复制// 判断是否为代理对象
if (AopUtils.isAopProxy(object)) {
// 获取目标对象
Object target = AopTargetUtils.getTarget(object);
}
5.2 自调用问题
问题现象: 代理对象方法内部调用另一个代理方法时,切面逻辑不生效
典型示例:
java复制class ServiceImpl implements Service {
public void a() {
this.b(); // 自调用不会触发代理
}
public void b() {
// ...
}
}
解决方案:
- 通过ApplicationContext获取代理实例
- 使用AopContext.currentProxy()
- 重构代码结构避免自调用
5.3 性能优化技巧
- 对高频调用方法,缓存Method对象而非每次都反射获取
- 在InvocationHandler中使用方法白名单过滤
- 对不需要代理的方法直接委托给目标对象
java复制class OptimizedHandler implements InvocationHandler {
private Object target;
private Map<String, Method> methodCache = new ConcurrentHashMap<>();
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 缓存加速
Method targetMethod = methodCache.computeIfAbsent(
method.getName(),
k -> findMethod(method)
);
// 白名单过滤
if ("fastPath".equals(method.getName())) {
return targetMethod.invoke(target, args);
}
// 正常代理逻辑
return doProxy(targetMethod, args);
}
}
6. 设计模式组合应用
6.1 代理+装饰器模式
- 代理:控制访问,重点在于"是否允许"
- 装饰器:增强功能,重点在于"如何增强"
组合应用示例:
java复制// 基础接口
interface DataService {
String fetchData();
}
// 原始实现
class RemoteDataService implements DataService {
public String fetchData() {
return "Data from remote";
}
}
// 装饰器:功能增强
class CachingDecorator implements DataService {
private DataService wrappee;
private String cache;
public String fetchData() {
if (cache == null) {
cache = wrappee.fetchData();
}
return "[Cached] " + cache;
}
}
// 代理:访问控制
class AuthProxy implements DataService {
private DataService realService;
private User user;
public String fetchData() {
if (!user.isAuthenticated()) {
throw new SecurityException("Authentication required");
}
return realService.fetchData();
}
}
// 组合使用
DataService service = new AuthProxy(
new CachingDecorator(
new RemoteDataService()
)
);
6.2 代理+工厂模式
通过工厂方法统一创建代理实例:
java复制class ProxyFactory {
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new StandardHandler(target)
);
}
private static class StandardHandler implements InvocationHandler {
// ...实现略
}
}
7. 现代框架中的代理应用
7.1 Spring AOP实现原理
Spring AOP默认使用动态代理实现,其核心工作流程:
- 通过
@EnableAspectJAutoProxy启用代理 - 解析
@Aspect注解的切面类 - 创建
AnnotationAwareAspectJAutoProxyCreator后置处理器 - 对匹配的Bean创建代理对象
- 将切面逻辑织入方法调用链
自定义Pointcut示例:
java复制@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object measurePerformance(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long duration = System.currentTimeMillis() - start;
System.out.printf("Method %s executed in %d ms%n",
pjp.getSignature(), duration);
return result;
}
}
7.2 MyBatis的Mapper代理
MyBatis通过JDK动态代理将接口调用转换为SQL执行:
java复制public class MapperProxy<T> implements InvocationHandler {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 解析方法对应的SQL语句
MappedStatement ms = sqlSession.getConfiguration()
.getMappedStatement(method.getName());
// 执行数据库操作
return sqlSession.selectOne(ms, args);
}
}
8. 代理模式最佳实践
-
接口设计原则
- 代理类和真实主题应实现相同接口
- 接口方法应保持单一职责
- 考虑将代理相关方法单独抽离
-
性能考量
- 对性能敏感的场景,考虑使用静态代理
- 动态代理应缓存Method对象
- 避免在代理层进行复杂计算
-
线程安全
- 代理对象通常需要保证线程安全
- 注意目标对象的状态管理
- 双重检查锁定要正确实现
-
调试技巧
- 通过
Proxy.isProxyClass()识别代理类 - 使用
Proxy.getInvocationHandler()获取处理器 - 在IDE中配置条件断点过滤代理调用
- 通过
java复制// 调试代理调用的条件断点示例
// 条件表达式:!Proxy.isProxyClass(this.getClass())
public void businessMethod() {
// 业务逻辑
}
在长期实践中我发现,合理使用代理模式可以显著提升系统的可维护性。特别是在需要统一处理横切关注点(如日志、事务、安全等)时,代理模式能保持业务代码的纯净性。但也要警惕过度使用导致的调试复杂度增加,建议控制在真正需要访问控制的场景使用。
