Java代理模式与装饰模式的应用与实现
发布时间 - 2026-01-10 00:00:00 点击率:次该用InvocationHandler而非继承当需不修改目标类即实现权限校验、日志记录等访问控制;JDK动态代理仅支持接口,调用method.invoke(target,args)不可省略,Spring中应启用AOP而非手动创建代理。
代理模式:什么时候该用 InvocationHandler 而不是继承?
代理模式的核心是「控制访问」,不是「增强行为」。如果你需要在不修改目标类的前提下,对方法调用做权限校验、日志记录、延迟加载或远程通信封装,Proxy.newProxyInstance() + InvocationHandler 是标准解法。
常见错误是把代理当成装饰器来用——比如只为加个缓存就写个代理,结果绕过类型安全、丢失泛型信息、还无法处理 final 方法。
- 只适用于接口:JDK 动态代理只能代理接口,不能代理具体类;要代理类得用 CGLIB(但会生成子类,
final类/方法直接失败) -
invoke()中必须显式调用method.invoke(target, args),漏掉这句就等于拦截后没转发,目标逻辑不会执行 - 如果目标对象本身是 Spring Bean,别手动 new
Proxy,应通过@EnableAspectJAutoProxy(proxyTargetClass = true)或@Scope("prototype")配合 AOP 使用,否则可能破坏 Spring 的生命周期管理
public class LoggingInvocationHandler implements InvocationHandler {
private final Object target;
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before " + method.getName());
Object result = method.invoke(target, args); // 必须调用!
System.out.println("After " + method.ge
tName());
return result;
}
}
装饰模式:为什么 InputStream 体系是教科书级实现?
装饰模式的关键是「职责叠加」,每个装饰器都持有被装饰对象的引用,并在自身方法中组合调用它。Java 标准库的 InputStream 子类就是典型:你可以在 FileInputStream 上套一层 BufferedInputStream,再套一层 GZIPInputStream,每一层只关心自己的逻辑,不侵入下层。
容易踩的坑是混淆「构造时传入」和「运行时替换」:装饰器一旦构建完成,内部引用的对象就不能动态切换;想换底层流,得重建整个装饰链。
立即学习“Java免费学习笔记(深入)”;
- 所有装饰器必须和被装饰类实现同一接口(如
InputStream),否则无法无缝替换 - 装饰器通常不重写全部方法,只覆盖关心的几个(如
read()),其余直接委托给被装饰对象 - 注意关闭顺序:应从最外层装饰器开始
close(),它内部会自动触发内层的close();但如果某层装饰器自己打开了资源(如BufferedOutputStream的缓冲区),必须确保其close()被调用,否则资源泄露
public class CountingInputStream extends InputStream {
private final InputStream in;
private long bytesRead = 0;
public CountingInputStream(InputStream in) {
this.in = in;
}
@Override
public int read() throws IOException {
int b = in.read();
if (b != -1) bytesRead++;
return b;
}
public long getBytesRead() { return bytesRead; }
}
代理 vs 装饰:一个接口,两种意图
两者代码结构相似(都持有一个目标对象并转发调用),但设计意图完全不同。判断依据不是“谁包着谁”,而是「谁拥有对象生命周期」和「谁决定是否调用目标」。
代理模式中,代理对象通常由框架创建(如 Spring AOP),客户端拿到的是代理,根本不知道真实对象存在;而装饰模式中,客户端主动组装装饰链,清楚每一层的作用,且能随时拆解某一层。
- 代理关注「访问控制」:
SecurityManager.checkPermission()在调用前抛异常,目标方法根本不执行 - 装饰关注「功能增强」:
BufferedInputStream把多次小读取合并成一次大读取,但最终仍会调用到底层read() - Spring 的
@Transactional是代理(事务开启/提交完全绕过业务方法逻辑);而Collections.unmodifiableList()返回的是装饰器(它把所有修改操作转为抛异常,但查询操作照常委托)
手写代理易忽略的线程与泛型问题
手动实现静态代理或简单动态代理时,最容易被忽略的是泛型擦除和线程安全性。JDK 代理返回的 Object 需要强制转型,而泛型信息在运行时已不存在,强转失败会在运行时报 ClassCastException,而不是编译期报错。
另一个隐性问题是 InvocationHandler 实例是否线程安全。如果它内部维护了状态(比如计数器、缓存 map),多个线程同时调用代理对象的方法,就会出现竞态条件。
- 避免在
InvocationHandler中保存非线程安全的可变状态;若必须缓存,用ConcurrentHashMap或加锁 - 使用
Proxy.newProxyInstance()时,ClassLoader参数建议传目标接口的类加载器(interface.getClass().getClassLoader()),避免跨 ClassLoader 导致ClassCastException - 如果目标接口有泛型方法(如
),代理无法保留类型参数,返回值需按实际类型强转,IDE 可能提示 unchecked warning,这是正常现象T get(String key)
# java
# ssl
# proxy
# stream
# 延迟加载
# 动态代理
# 标准库
# 为什么
# red
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
如何在宝塔面板创建新站点?
Laravel如何发送系统通知?(Notification渠道示例)
Laravel如何实现API速率限制?(Rate Limiting教程)
如何在沈阳梯子盘古建站优化SEO排名与功能模块?
通义万相免费版怎么用_通义万相免费版使用方法详细指南【教程】
百度输入法全感官ai怎么关 百度输入法全感官皮肤关闭
Laravel PHP版本要求一览_Laravel各版本环境要求对照
什么是javascript作用域_全局和局部作用域有什么区别?
如何注册花生壳免费域名并搭建个人网站?
高端企业智能建站程序:SEO优化与响应式模板定制开发
Win11怎样安装网易有道词典_Win11安装词典教程【步骤】
JavaScript Ajax实现异步通信
详解Oracle修改字段类型方法总结
Android Socket接口实现即时通讯实例代码
美食网站链接制作教程视频,哪个教做美食的网站比较专业点?
Laravel如何编写单元测试和功能测试?(PHPUnit示例)
香港服务器建站指南:免备案优势与SEO优化技巧全解析
Laravel如何配置和使用队列处理异步任务_Laravel队列驱动与任务分发实例
详解jQuery中的事件
Laravel怎么生成URL_Laravel路由命名与URL生成函数详解
,怎么在广州志愿者网站注册?
JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)
香港服务器租用每月最低只需15元?
Windows10如何删除恢复分区_Win10 Diskpart命令强制删除分区
如何快速使用云服务器搭建个人网站?
如何撰写建站申请书?关键要点有哪些?
如何快速配置高效服务器建站软件?
Laravel如何与Docker(Sail)协同开发?(环境搭建教程)
微信小程序 配置文件详细介绍
今日头条AI怎样推荐抢票工具_今日头条AI抢票工具推荐算法与筛选【技巧】
如何使用 Go 正则表达式精准提取括号内首个纯字母标识符(忽略数字与嵌套)
edge浏览器无法安装扩展 edge浏览器插件安装失败【解决方法】
消息称 OpenAI 正研发的神秘硬件设备或为智能笔,富士康代工
Laravel如何使用Blade组件和插槽?(Component代码示例)
Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】
Laravel如何优化应用性能?(缓存和优化命令)
Laravel如何与Pusher实现实时通信?(WebSocket示例)
历史网站制作软件,华为如何找回被删除的网站?
如何快速搭建高效服务器建站系统?
Windows10电脑怎么设置虚拟光驱_Win10右键装载ISO镜像文件
PHP正则匹配日期和时间(时间戳转换)的实例代码
Java解压缩zip - 解压缩多个文件或文件夹实例
Laravel用户密码怎么加密_Laravel Hash门面使用教程
千库网官网入口推荐 千库网设计创意平台入口
Laravel如何使用Eloquent进行子查询
如何在七牛云存储上搭建网站并设置自定义域名?
香港服务器WordPress建站指南:SEO优化与高效部署策略
Linux后台任务运行方法_nohup与&使用技巧【技巧】
高端建站如何打造兼具美学与转化的品牌官网?
mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?


tName());
return result;
}
}