如何让Java模块内类对非模块化项目不可见?及设计合理性问询
Java模块未导出包的公共类可被非模块化项目访问:这是设计,不是Bug
嘿,这绝对不是Bug,而是Java模块系统(JPMS)特意做的兼容设计,核心目的就是让新的模块化代码能和海量传统非模块化项目和平共处,不至于一升级就崩溃。
背后的逻辑:未命名模块的特殊权限
Java 9引入模块系统时,最大的难题就是如何让新老代码无缝兼容。于是就有了**未命名模块(Unnamed Module)**这个关键概念:
- 所有没有
module-info.java、运行在classpath上的代码(比如你的sub-recipient项目),都属于未命名模块。 - 未命名模块有个特殊特权:它可以读取所有模块化JAR里未导出的公共类,就好像这些包被自动导出了一样。
- 反过来,模块化代码也能读取未命名模块的所有类,双向兼容保证了过渡的平滑性。
对应你的场景来看
你的例子正好完美契合这个设计规则:
- 当
sub-recipient是非模块化项目时,它属于未命名模块,所以能绕过my.donor模块的导出限制,直接访问Cowboy这个公共类,编译运行都毫无问题。 - 一旦给
sub-recipient加上module-info.java变成模块化项目,它就不再是未命名模块了,必须严格遵循模块系统的封装规则,这时候就访问不到未导出的Cowboy了,完全符合预期。
为什么要做这样的设计?
如果强制要求非模块化项目只能访问模块化项目显式导出的包,那大量依赖第三方库的传统项目升级到Java 9+时直接就会报错——毕竟很多库要么刚起步做模块化改造,要么根本没做。这个设计给了开发者一个过渡缓冲期:
- 可以先把部分项目改成模块化,不影响原有非模块化项目的正常使用。
- 等所有依赖都完成模块化改造后,再严格按照导出规则来,实现真正的封装隔离。
如果想避免这种"意外可见"怎么办?
要是你的模块化项目真的不想让未命名模块碰内部的公共类,有几个可行的办法:
- 把内部的公共类改成包私有(删掉
public修饰符),这样不管对方是不是模块化项目,都无法访问。 - 用编译/运行参数限制:Java 9到15可以用
--illegal-access=deny,强制禁止未命名模块访问未导出的包;Java 16之后这个参数被移除了,可以用--add-opens或--add-exports做更精细的控制,但这俩更多是给旧代码兼容用的,不是用来限制的。
内容的提问来源于stack exchange,提问作者diziaq
相关产品推荐
相关产品推荐

