<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Java 动态模块系统 | Java dynamic module system]]></title><description><![CDATA[<h2>前言</h2>
<p dir="auto">在使用高版本的时候, 总会不可避免的接触到模块系统, 比如反射操作 <code>java.base</code> 已经十分困难. 既然 JDK 内部可以享受到模块的保护, 那么我们自己的代码是否也可以享受到模块系统的保护呢</p>
<p dir="auto">当然可以，而且也不是非常麻烦。</p>
<p dir="auto">使用模块，你将面对以下问题</p>
<ul>
<li><span style="color:green">得到反射保护, 外部代码将不能通过反射强行修改/调用私有成员</span></li>
<li><span style="color:red">更严格的访问控制, 不能直接访问非 required 的模块</span></li>
<li><span style="color:red">失去 <code>--add-opens=....=ALL-UNNAMED</code> 的归属判断</span></li>
<li><span style="color:gray">需要专门的 ClassLoader / 需要自行实现 ClassLoader</span></li>
</ul>
<p dir="auto">使用模块的适用情况</p>
<ul>
<li>需要编写严格的附属(插件)系统</li>
<li>需要保护自身代码/保护自身内存空间</li>
<li><s>觉得弄着好玩</s></li>
</ul>
<h2>定义一个模块</h2>
<blockquote>
<p dir="auto">注: 此处的定义指的是, 通过运行时代码在运行时定义一个模块.<br />
而不是大多数资料说的直接写一个 <code>module-info.java</code></p>
</blockquote>
<p dir="auto">要定义一个模块, 首先需要一个模块的描述符文件 (<code>ModuleDescriptor</code>), 可以从以编码文件读取 (<code>ModuleDescriptor.read(InputStream) &lt;- module-info.class</code>), 也可以在运行时动态生成一个(<code>ModuleDescriptor.newModule("name_of_module").build()</code>)</p>
<p dir="auto">jvm 通过包来区分模块, 而一个模块的全部包都需要提前指定, jvm 才会为这些包分配到一个模块内</p>
<pre><code class="language-java">var moduleDescriptor = ModuleDescriptor.newModule("my.custom_module")
    .packages(Set.of("io.github.karlatemp.jmse.main"))
    .exports("io.github.karlatemp.jmse.main")
    .build();
</code></pre>
<p dir="auto">这里我们已经拥有了一个模块的描述符, 现在我们还需要一个模块描述的引用, 以及一个模块查找器以让 jvm 可以找到我们的模块</p>
<pre><code class="language-java">var myModuleReference = new ModuleReference(
    moduleDescriptor, null
) {
    @Override public ModuleReader open() throws IOException {
        throw new UnsupportedOperationException();
    }
}
var myModuleFinder = new ModuleFinder() {
    @Override
    public Optional&lt;ModuleReference&gt; find(String name) {
        if (name.equals(moduleDescriptor.name())) {
            return Optional.of(myModuleReference);
        }
        return Optional.empty();
    }

    @Override
    public Set&lt;ModuleReference&gt; findAll() {
        return Set.of(myModuleReference);
     }
};
</code></pre>
<p dir="auto">最后，定义一个模块</p>
<pre><code class="language-java">var bootLayer = ModuleLayer.boot();
var myConfiguration = bootLayer.configuration().resolve(
    myModuleFinder, ModuleFinder.of(), Set.of(moduleDescriptor.name())
);
var classLoader = ClassLoader.getSystemClassLoader();
var controller = ModuleLayer.defineModules(
    myConfiguration, List.of(bootLayer), $ -&gt; classLoader
);

Class.forName("io.github.karlatemp.jmse.main.ModuleMain", false, classLoader)
    .getMethod("launch")
    .invoke(null);
</code></pre>
<h2>ServiceLoader / Class.forName(Module, String)</h2>
<p dir="auto">还记得 <code>需要专门的 ClassLoader / 需要自行实现 ClassLoader</code> 吗, 虽然在上文已经成功定义了一个模块，但是只要使用 <code>ServiceLoader</code> / <code>Class.forName(Module, String)</code>, 那么将无法找到对应的类, 因为一般的 ClassLoader 并没有专门处理动态加载的模块</p>
<h3>Analyze</h3>
<p dir="auto">通过进行调用分析, 最终可以发现以上两个东西最终都进入到了下面的方法</p>
<pre><code class="language-java">public class ClassLoader {
    final Class&lt;?&gt; loadClass(Module module, String name) {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class&lt;?&gt; c = findLoadedClass(name);
            if (c == null) {
                c = findClass(module.getName(), name);
            }
            if (c != null &amp;&amp; c.getModule() == module) {
                return c;
            } else {
                return null;
            }
        }
    }
    protected Class&lt;?&gt; findClass(String moduleName, String name) {
        if (moduleName == null) {
            try {
                return findClass(name);
            } catch (ClassNotFoundException ignore) { }
        }
        return null;
    }
}
</code></pre>
<p dir="auto">不难发现, 由于默认没有处理模块, 导致指定搜索模块的时候将搜索不到动态定义的模块</p>
<p dir="auto">而 <code>jdk.internal.loader.ClassLoader$AppClassLoader</code> 并没有处理通过 <code>ModuleLayer.defineModule</code> 定义的模块, 于是也不能直接将模块定义到系统类加载器</p>
<h3>自行实现类加载器</h3>
<p dir="auto">自行实现类加载器十分简单，只需要</p>
<pre><code class="language-java">public class MyCustomClassLoader extends URLClassLoader {
    String moduleName;

    @Override
    protected Class&lt;?&gt; findClass(String moduleName, String name) {
        // System.out.println("Find class: " + moduleName + "/" + name);
        if (this.moduleName.equals(moduleName)) {
            try {
                return findClass(name);
            } catch (ClassNotFoundException ignored) {
            }
        }
        return super.findClass(moduleName, name);
    }
}
</code></pre>
<h3>使用 JDK 内置的类加载器</h3>
<p dir="auto">只需要实现 <code>ModuleReference.open(): ModuleReader</code>, 然后使用</p>
<pre><code class="language-java">var controller = ModuleLayer.defineModulesWithOneLoader(
        myConfiguration,
        List.of(bootLayer),
        ClassLoader.getSystemClassLoader().getParent()
);
var classLoader = controller.layer().findLoader(moduleDescriptor.name());
</code></pre>
<p dir="auto">即可使用 JDK 内置的内加载器</p>
<hr />
<h2>完整参考</h2>
<ul>
<li><a href="https://github.com/Karlatemp/java-module-system-explore" target="_blank" rel="noopener noreferrer nofollow ugc">java-module-system-explore</a>
<ul>
<li><a href="https://github.com/Karlatemp/java-module-system-explore/blob/master/src/main/java/io/github/karlatemp/jmse/boot/BootModuleByStandard.java" target="_blank" rel="noopener noreferrer nofollow ugc">BootModuleByStandard.java</a></li>
<li><a href="https://github.com/Karlatemp/java-module-system-explore/blob/master/src/main/java/io/github/karlatemp/jmse/boot/MyCustomClassLoader.java" target="_blank" rel="noopener noreferrer nofollow ugc">MyCustomClassLoader.java</a></li>
</ul>
</li>
</ul>
]]></description><link>https://mirai.mamoe.net/topic/936/java-动态模块系统-java-dynamic-module-system</link><generator>RSS for Node</generator><lastBuildDate>Mon, 16 Mar 2026 17:44:52 GMT</lastBuildDate><atom:link href="https://mirai.mamoe.net/topic/936.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 20 Jan 2022 05:50:00 GMT</pubDate><ttl>60</ttl></channel></rss>