使用URLClassLoader加载运行JUnit测试时资源路径错误问题咨询
路径异常的核心原因是ClassLoader.getSystemClassLoader()返回的系统类加载器,其资源搜索范围仅覆盖启动当前JVM进程的项目1的classpath路径,完全感知不到你用来加载项目2类的自定义URLClassLoader中挂载的项目2测试类、测试资源路径,因此读取资源时只会从项目1目录下查找,和预期路径不符。
方案1:替换系统类加载器(无需修改项目2源码,优先选择)
启动项目1的JVM时,可通过JVM启动参数指定系统类加载器为你自定义的、包含项目2全量classpath的类加载器;也可以在执行测试前通过反射临时替换系统类加载器实例,确保getSystemClassLoader()返回的实例能搜索到项目2的所有资源路径。
自定义URLClassLoader初始化时,必须把项目2的target/test-classes、target/classes目录,以及项目2所有依赖的jar包路径全部加入URL列表,避免类、资源缺失。
反射替换系统类加载器的核心代码示例:// 提前初始化好已挂载项目2全量类、资源路径的urlClassLoader实例 Field sclField = ClassLoader.class.getDeclaredField("scl"); sclField.setAccessible(true); sclField.set(null, urlClassLoader);替换完成后再启动JUnit测试执行逻辑,测试方法里调用
getSystemClassLoader().getResourceAsStream()时就会从挂载的项目2路径下查找资源。
注意JDK9及以上版本使用反射修改系统类加载器时,需要给JVM添加启动参数--add-opens java.base/java.lang=ALL-UNNAMED解除模块访问限制。方案2:修正项目2测试代码的资源读取逻辑
把测试代码里硬编码的ClassLoader.getSystemClassLoader().getResourceAsStream()替换为当前测试类自身的类加载器来读取资源,修改示例:// 替换原有的系统类加载器调用逻辑 InputStream is = JiraCsv248Test.class.getClassLoader().getResourceAsStream(resourcePath); // 更稳妥的写法是直接用Class对象的getResourceAsStream方法,路径默认相对于当前类所在包 // InputStream is = JiraCsv248Test.class.getResourceAsStream(resourcePath);这种写法会自动使用加载当前测试类的
urlClassLoader查找资源,不需要修改全局类加载器逻辑,只要自定义类加载器正确挂载了项目2的资源路径就能正常读取。方案3:将项目2测试路径加入项目1classpath
如果不需要隔离两个项目的classpath,可以直接在项目1的Maven配置中引入项目2的测试依赖,把项目2的测试类、测试资源直接加入项目1的系统类加载器搜索路径。这种方式不需要自定义类加载器,直接用默认类加载器就能执行测试、读取资源,但会失去两个项目的类加载隔离性。
注意:如果选择自定义类加载器方案,执行JUnit测试时需要把JUnit核心类也纳入类加载器的可见范围,避免出现类转换异常、类链接错误。
内容的提问来源于stack exchange,提问作者sacp

