该用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.getName());
return result;
}
}
装饰模式:为什么 InputStream 体系是教科书级实现?
装饰模式的关键是「职责叠加」,每个装饰器都持有被装饰对象的引用,并在自身方法中组合调用它。Java 标准库的 InputStream 子类就是典型:你可以在 FileInputStream 上套一层 BufferedInputStream,再套一层 GZIPInputStream,每一层只关心自己的逻辑,不侵入下层。
容易踩的坑是混淆「构造时传入」和「运行时替换」:装饰器一旦构建完成,内部引用的对象就不能动态切换;想换底层流,得重建整个装饰链。
立即学习“Java免费学习笔记(深入)”;
该系统采用多层模式开发,这个网站主要展示女装的经营,更易于网站的扩展和后期的维护,同时也根据常用的SQL注入手段做出相应的防御以提高网站的安全性,本网站实现了购物车,产品订单管理,产品展示,等等,后台实现了动态权限的管理,客户管理,订单管理以及商品管理等等,前台页面设计精致,后台便于操作等。实现了无限子类的添加,实现了动态权限的管理,支持一下一个人做的辛苦
- 所有装饰器必须和被装饰类实现同一接口(如
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)









