APS 系列文章 08

JPMS 强封装:--add-opens 与快速失败

Java 9 引入模块系统(JPMS)后,跨模块访问 class 有了更强的封装约束。动态代理因为要在目标类所在的包内生成字节码,首当其冲。

APS 对这块的态度是:不硬来,快速失败并给出可操作的提示

类代理怎么定义类

类代理需要在目标类同一个包 里生成子类(这样才能访问包私有成员、合法 extends)。APS 用的是 MethodHandles.privateLookupIn 拿到目标类的私有 Lookup,再定义隐藏类。

当目标类在一个强封装模块 里时------任何没有 open 的包,包括 java.base 里的 java.util 这种------这个 private lookup 会被 JVM 拒绝。于是 proxy() 立刻失败,并抛出一条带 --add-opens 建议的异常。

一个真实报错

下面这段代码尝试代理 java.util.ArrayList

java 复制代码
try {
    AcceleratedProxy.proxy(ArrayList.class,
            (o, m, a) -> AcceleratedProxy.invokeSuper(o, m, a));
} catch (RuntimeException e) {
    System.out.println(e.getCause().getMessage());
}

输出:

text 复制代码
Cannot access java.util.ArrayList in module java.base (package java.util):
the package is not open to the unnamed module. Add --add-opens
java.base/java.util=ALL-UNNAMED to the JVM arguments, or declare
'opens java.util;' in the module's module-info.java.

这条消息把两件事都告诉你了:为什么失败 (包没 open),以及怎么修

两种修复方式

方式一:加 JVM 参数 (对 java.base 这种第三方模块常用):

bash 复制代码
java --add-opens java.base/java.util=ALL-UNNAMED ...

方式二:在目标模块声明 open(你自己的模块):

java 复制代码
module my.module {
    opens com.example.internal;   // 把包 open 出来
}

接口代理为什么不受影响

和类代理不同,接口代理用的是 public Lookup ,所以它只支持 public 接口------这和 java.lang.reflect.Proxy 的约束完全一致。非 public 接口代理则走 LookupManager,把生成类定义在接口自己的包里(上一篇讲过)。

所以「JPMS 强封装」这条限制,只针对类代理,接口代理不碰 private lookup。

设计上的取舍

值得点出来的是「快速失败」这个选择。很多库在这里会静默降级(比如偷偷换一个更慢但能工作的路径),结果是用户半年后才发现性能不对。APS 选择当场抛异常 + 明确提示 ,把决策权交给调用方------要么加 --add-opens,要么换接口代理。

这也呼应了本系列第一篇的核心观点:APS 的性能优势来自「直接 INVOKESPECIAL 调用父类方法」,而这要求代理类定义在目标类的包里。JPMS 的强封装直接堵死了这条路,所以必须显式 open,没有中间态。

小结

JPMS 强封装是类代理绕不开的一道坎。APS 的处理是:接口代理不受影响、类代理遇到强封装模块快速失败并给出 --add-opens 提示。理解这一点,你在模块化项目里就能提前规划好哪些包需要 open。

下一篇正式进入原理,回答那个贯穿全系列的问题:invokeSuper 到底是怎么变成 INVOKESPECIAL

本文示例代码见 aps-blog/examples,框架源码与完整文档见 APS 仓库

相关推荐
萧瑟余晖2 小时前
Java深入解析篇三十三之消息队列
java·开发语言
码匠许师傅2 小时前
【C++ 面试真题】聊聊 C++ 的序列容器
java·c++·面试
IT爱学堂2 小时前
尚硅谷Java+AI大模型应用开发革新版本 2025年3月
java·开发语言·人工智能
不爱编程的小九九2 小时前
小九源码-springboot004-springboot智能阅读推荐系统
java·spring boot·后端
SCandL1523 小时前
Docker的实战应用
java·docker·容器
AC赳赳老秦3 小时前
OpenClaw 多源采集公开行业数据:从原始信息到研究报告初稿的自动化实践
java·c语言·c++·python·php·deepseek·openclaw
Java成神之路-4 小时前
深入理解三大高频队列:ArrayDeque、LinkedList、PriorityQueue 方法、特性与场景选择
java
roman_日积跬步-终至千里4 小时前
【资源控制】自助查询的智能路由
java·大数据·数据库
步行cgn4 小时前
MyBatis Error evaluating expression ‘ids‘. Return value (3) was not iterable 错误详
java·后端