前言
在使用高版本的时候, 总会不可避免的接触到模块系统, 比如反射操作 java.base 已经十分困难. 既然 JDK 内部可以享受到模块的保护, 那么我们自己的代码是否也可以享受到模块系统的保护呢
当然可以,而且也不是非常麻烦。
使用模块,你将面对以下问题
- 得到反射保护, 外部代码将不能通过反射强行修改/调用私有成员
- 更严格的访问控制, 不能直接访问非 required 的模块
- 失去 --add-opens=....=ALL-UNNAMED的归属判断
- 需要专门的 ClassLoader / 需要自行实现 ClassLoader
使用模块的适用情况
- 需要编写严格的附属(插件)系统
- 需要保护自身代码/保护自身内存空间
- 觉得弄着好玩
定义一个模块
注: 此处的定义指的是, 通过运行时代码在运行时定义一个模块.
而不是大多数资料说的直接写一个 module-info.java
要定义一个模块, 首先需要一个模块的描述符文件 (ModuleDescriptor), 可以从以编码文件读取 (ModuleDescriptor.read(InputStream) <- module-info.class), 也可以在运行时动态生成一个(ModuleDescriptor.newModule("name_of_module").build())
jvm 通过包来区分模块, 而一个模块的全部包都需要提前指定, jvm 才会为这些包分配到一个模块内
| 12
 3
 4
 
 | var moduleDescriptor = ModuleDescriptor.newModule("my.custom_module").packages(Set.of("io.github.karlatemp.jmse.main"))
 .exports("io.github.karlatemp.jmse.main")
 .build();
 
 | 
这里我们已经拥有了一个模块的描述符, 现在我们还需要一个模块描述的引用, 以及一个模块查找器以让 jvm 可以找到我们的模块
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | var myModuleReference = new ModuleReference(moduleDescriptor, null
 ) {
 @Override public ModuleReader open() throws IOException {
 throw new UnsupportedOperationException();
 }
 }
 var myModuleFinder = new ModuleFinder() {
 @Override
 public Optional<ModuleReference> find(String name) {
 if (name.equals(moduleDescriptor.name())) {
 return Optional.of(myModuleReference);
 }
 return Optional.empty();
 }
 
 @Override
 public Set<ModuleReference> findAll() {
 return Set.of(myModuleReference);
 }
 };
 
 | 
最后,定义一个模块
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | 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), $ -> classLoader
 );
 
 Class.forName("io.github.karlatemp.jmse.main.ModuleMain", false, classLoader)
 .getMethod("launch")
 .invoke(null);
 
 | 
ServiceLoader / Class.forName(Module, String)
还记得 需要专门的 ClassLoader / 需要自行实现 ClassLoader 吗, 虽然在上文已经成功定义了一个模块,但是只要使用 ServiceLoader / Class.forName(Module, String), 那么将无法找到对应的类, 因为一般的 ClassLoader 并没有专门处理动态加载的模块
Analyze
通过进行调用分析, 最终可以发现以上两个东西最终都进入到了下面的方法
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | public class ClassLoader {final Class<?> loadClass(Module module, String name) {
 synchronized (getClassLoadingLock(name)) {
 
 Class<?> c = findLoadedClass(name);
 if (c == null) {
 c = findClass(module.getName(), name);
 }
 if (c != null && c.getModule() == module) {
 return c;
 } else {
 return null;
 }
 }
 }
 protected Class<?> findClass(String moduleName, String name) {
 if (moduleName == null) {
 try {
 return findClass(name);
 } catch (ClassNotFoundException ignore) { }
 }
 return null;
 }
 }
 
 | 
不难发现, 由于默认没有处理模块, 导致指定搜索模块的时候将搜索不到动态定义的模块
而 jdk.internal.loader.ClassLoader$AppClassLoader 并没有处理通过 ModuleLayer.defineModule 定义的模块, 于是也不能直接将模块定义到系统类加载器
自行实现类加载器
自行实现类加载器十分简单,只需要
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | public class MyCustomClassLoader extends URLClassLoader {String moduleName;
 
 @Override
 protected Class<?> findClass(String moduleName, String name) {
 
 if (this.moduleName.equals(moduleName)) {
 try {
 return findClass(name);
 } catch (ClassNotFoundException ignored) {
 }
 }
 return super.findClass(moduleName, name);
 }
 }
 
 | 
使用 JDK 内置的类加载器
只需要实现 ModuleReference.open(): ModuleReader, 然后使用
| 12
 3
 4
 5
 6
 
 | var controller = ModuleLayer.defineModulesWithOneLoader(myConfiguration,
 List.of(bootLayer),
 ClassLoader.getSystemClassLoader().getParent()
 );
 var classLoader = controller.layer().findLoader(moduleDescriptor.name());
 
 | 
即可使用 JDK 内置的内加载器
完整参考