前言
在使用高版本的时候, 总会不可避免的接触到模块系统, 比如反射操作 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 才会为这些包分配到一个模块内
1 2 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 可以找到我们的模块
1 2 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); } };
|
最后,定义一个模块
1 2 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
通过进行调用分析, 最终可以发现以上两个东西最终都进入到了下面的方法
1 2 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
定义的模块, 于是也不能直接将模块定义到系统类加载器
自行实现类加载器
自行实现类加载器十分简单,只需要
1 2 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
, 然后使用
1 2 3 4 5 6
| var controller = ModuleLayer.defineModulesWithOneLoader( myConfiguration, List.of(bootLayer), ClassLoader.getSystemClassLoader().getParent() ); var classLoader = controller.layer().findLoader(moduleDescriptor.name());
|
即可使用 JDK 内置的内加载器
完整参考