Java 17反射调用外部Jar报错:无法访问URLClassLoader.addURL
问题
我有一段用于加载外部Jar文件中定义方法的代码,在基于Spring Boot 2.7.10的Web应用里可在运行时调用这些外部方法。该代码在Java 11下运行正常,但升级到Java 17后出现如下错误:
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected void java.net.URLClassLoader.addURL(java.net.URL) accessible: module java.base does not "opens java.net" to unnamed module @221af3c0
代码实现如下:
private static void addToClassPath(String jarFilePath) { try { File file = new File(jarFilePath); if (!(file.exists() && file.canRead())) { throw new UdfDirectoryNotAccessibleException(jarFilePath); } URL url = file.toURI().toURL(); URLClassLoader urlClassLoader = (URLClassLoader) UdfInvoker.class.getClassLoader(); Class urlClass = URLClassLoader.class; Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class}); // error here since java 17 method.setAccessible(true); method.invoke(urlClassLoader, new Object[] { url }); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) { throw new UdfFailedLoadingException(jarFilePath, ex.getMessage(), ex); } catch (MalformedURLException ex) { throw new UdfJarFileNotAccessibleException(jarFilePath, ex.getMessage(), ex); } }
要求修改代码兼容Java 17,或提供同时兼容Java 11和17的替代方案,且:
- 无法修改Tomcat容器或开发机器JRE的JDK参数;
- 不能降级到Java 11版本。
解决方案
1. 自定义URLClassLoader(兼容Java 11/17)
Java 17强化了模块系统的访问控制,URLClassLoader的addURL方法无法再通过反射突破限制。可以自定义类加载器继承URLClassLoader,直接暴露addURL方法:
public class CustomJarClassLoader extends URLClassLoader { public CustomJarClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } @Override public void addURL(URL url) { super.addURL(url); } }
修改原加载逻辑,用自定义类加载器加载外部Jar:
private static ClassLoader addToClassPath(String jarFilePath) { try { File file = new File(jarFilePath); if (!(file.exists() && file.canRead())) { throw new UdfDirectoryNotAccessibleException(jarFilePath); } URL url = file.toURI().toURL(); // 以当前类加载器为父加载器,创建自定义类加载器 CustomJarClassLoader classLoader = new CustomJarClassLoader(new URL[]{url}, UdfInvoker.class.getClassLoader()); return classLoader; } catch (MalformedURLException ex) { throw new UdfJarFileNotAccessibleException(jarFilePath, ex.getMessage(), ex); } }
后续调用外部Jar方法时,需使用该自定义类加载器加载目标类:
ClassLoader customLoader = addToClassPath("path/to/your.jar"); Class<?> udfClass = customLoader.loadClass("com.example.YourUdfClass"); Object udfInstance = udfClass.getDeclaredConstructor().newInstance(); Method method = udfClass.getDeclaredMethod("yourTargetMethod"); method.invoke(udfInstance);
2. 利用Spring工具类简化加载(适配Spring Boot场景)
作为Spring Boot应用,可借助PathMatchingResourcePatternResolver快速定位外部Jar资源,创建独立类加载器:
import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.util.ClassUtils; private static ClassLoader loadExternalJar(String jarFilePath) { try { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource jarResource = resolver.getResource("file:" + jarFilePath); ClassLoader parentClassLoader = ClassUtils.getDefaultClassLoader(); return new URLClassLoader(new URL[]{jarResource.getURL()}, parentClassLoader); } catch (IOException ex) { throw new UdfJarFileNotAccessibleException(jarFilePath, ex.getMessage(), ex); } }
3. 核心思路说明
两种方案本质都是不修改原有系统类加载器,而是创建独立的类加载器隔离加载外部Jar,既绕过了Java 17的模块访问限制,还能避免外部Jar与应用内部类的冲突问题。
内容的提问来源于stack exchange,提问作者Bằng Rikimaru
相关产品推荐
相关产品推荐

