如何将自定义类注入java.base模块引导类加载器以规避add-opens配置
问题本质
你虽然将类注入到了引导类加载器,但Java 9+模块系统下,引导类加载器加载的自定义类默认属于未命名模块,依然无法访问java.base模块未导出的sun.nio.ch包,无需修改启动参数,两种方案都可实现需求:
方案1:将自定义类注入到java.base模块(匹配你的需求)
方式A:直接在ByteBuddy注入时指定目标模块
较新版本的ByteBuddy原生支持指定注入的目标模块,直接修改你的注入逻辑即可:
public static synchronized void initialize() { if (!INITIALIZED) { TypePool typePool = TypePool.Default.ofSystemLoader(); Map<TypeDescription, byte[]> types = new ByteBuddy() .redefine( typePool.describe("pbouda.agents.core.Utils").resolve(), ClassFileLocator.ForClassLoader.ofSystemLoader()) .make() .getAllTypes(); // 获取java.base模块,注入时直接指定 Module javaBaseModule = Object.class.getModule(); ClassInjector.UsingUnsafe.ofBootLoader() .withModule(javaBaseModule) .inject(types); INITIALIZED = true; } }
方式B:注入后反射修改类所属模块
如果你的ByteBuddy版本不支持withModule方法,也可以注入完成后手动修改类的模块归属:
// 原有注入逻辑执行完成后新增以下代码 try { // 从引导类加载器获取已注入的Utils类 Class<?> utilsClazz = Class.forName("pbouda.agents.core.Utils", false, null); Module javaBaseModule = Object.class.getModule(); // 反射修改Class的module字段,将Utils类归属改为java.base模块 Field moduleField = Class.class.getDeclaredField("module"); moduleField.setAccessible(true); moduleField.set(utilsClazz, javaBaseModule); // 给反射调用的方法加accessible,避免后续调用权限问题 Field fdValMethodField = utilsClazz.getDeclaredField("FD_VALUE_METHOD"); fdValMethodField.setAccessible(true); Method fdValMethod = (Method) fdValMethodField.get(null); fdValMethod.setAccessible(true); } catch (Exception e) { e.printStackTrace(); }
修改后Utils类就归属java.base模块,同模块内可访问所有包资源,无需额外导出配置。
方案2:动态开放模块访问权限(无需修改类所属模块)
如果不想修改类的所属模块,也可以直接调用JDK内部的模块管理接口,动态给Utils类所在模块开放sun.nio.ch包的访问权限:
// 原有注入逻辑执行完成后新增以下代码 try { Class<?> utilsClazz = Class.forName("pbouda.agents.core.Utils", false, null); Module utilsModule = utilsClazz.getModule(); Module javaBaseModule = Class.forName("sun.nio.ch.IOUtil").getModule(); // 调用JDK内部方法添加导出规则 Method addExportsMethod = Class.forName("jdk.internal.module.Modules") .getDeclaredMethod("addExports", Module.class, String.class, Module.class); addExportsMethod.setAccessible(true); addExportsMethod.invoke(null, javaBaseModule, "sun.nio.ch", utilsModule); // 给反射方法设置accessible Field fdValMethodField = utilsClazz.getDeclaredField("FD_VALUE_METHOD"); fdValMethodField.setAccessible(true); Method fdValMethod = (Method) fdValMethodField.get(null); fdValMethod.setAccessible(true); } catch (Exception e) { e.printStackTrace(); }
说明
两种方案都不需要修改JVM启动参数,只要你的Agent本身已经拿到了默认的Agent权限即可正常运行。方案1更彻底,归属java.base模块后,后续要访问java.base内其他未导出的包也不需要额外配置;方案2更轻量化,仅开放所需的包权限,不会修改类的原有归属。
内容的提问来源于stack exchange,提问作者Petr Bouda
相关产品推荐
相关产品推荐

