以下代码将jar文件添加到构建路径,它适用于Java 8.但是,它会抛出Java 9的异常,该异常与转换为URLClassLoader有关。有什么想法可以解决这个问题吗?最佳解决方案将对其进行编辑,以便与Java 8和Java 8兼容。 9。
private static int AddtoBuildPath(File f) {
try {
URI u = f.toURI();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(urlClassLoader, u.toURL());
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException | MalformedURLException | IllegalAccessException ex) {
return 1;
}
return 0;
}
答案 0 :(得分:3)
你遇到the system class loader is no longer a URLClassLoader
这个事实。正如ClassLoader::getSystemClassLoader
的返回类型所示,这是一个实现细节,尽管是一个不可忽略的代码量依赖的实现细节。
根据评论判断,您正在寻找一种在运行时动态加载类的方法。作为Alan Bateman points out,这不能通过附加到类路径在Java 9中完成。
您应该考虑为此创建一个新的类加载器。这有一个额外的好处,你将能够摆脱新类,因为它们没有加载到应用程序类加载器中。如果您正在编译Java 9,那么您应该阅读layers - 它们为您提供了一个干净的抽象来加载一个全新的模块图。
答案 1 :(得分:3)
前一段时间,我偶然发现了这个问题。我使用了很多与问题类似的方法
private static int AddtoBuildPath(File f)
在运行时动态地将路径添加到类路径。问题中的代码在多个方面可能是不好的样式:1)假设ClassLoader.getSystemClassLoader()
返回URLClassLoader
是未记录的实现细节,并且2)使用反射使addURL
公开可能是另一个一个。
更干净的动态添加类路径的方法
如果您需要使用其他类路径URL来通过“ Class.forName
”进行类加载,则以下是一种干净,优雅且兼容(Java 8到10)的解决方案:
1)通过扩展URL类加载器并使用公共addURL
方法来编写自己的类加载器
public class MyClassloader extends URLClassLoader {
public MyClassloader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
2)声明类加载器的一个对象(单个/应用范围内)
private final MyClassloader classLoader;
并通过
实例化classLoader = new MyClassloader(new URL[0], this.getClass().getClassLoader());
注意:系统类加载器是父级。通过classLoader
加载的类知道可以通过this.getClass().getClassLoader()
加载的类,但反之则不然。
3)在需要时(动态)添加其他类路径:
File file = new File(path);
if(file.exists()) {
URL url = file.toURI().toURL();
classLoader.addURL(url);
}
4)通过单例类加载器通过
实例化对象或应用cls = Class.forName(name, true, classLoader);
注意:由于类加载器在加载类之前尝试了对父类加载器的委托(父类对其父对象的委托),因此您必须确保要加载的类对父类加载器不可见,以确保它是通过给定的类加载器加载的。为了使这一点更清楚:如果您的系统类路径上有ClassPathB
,然后在自定义ClassPathB
中添加ClassPathA
和一些classLoader
,则ClassPathB
下的类将通过系统类加载器加载,并且ClassPathA
下的类对其未知。但是,如果从系统类路径中删除ClassPathB
,则将通过自定义classLoader
加载此类,然后ClassPathB下的类将被ClassPathA下的类所知道。
5)您可以考虑通过以下方式将类加载器传递给线程:
setContextClassLoader(classLoader)
如果该线程使用getContextClassLoader
。
答案 2 :(得分:2)
Shadov指向oracle community的帖子。有正确的答案:
Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));
提到的警告也很重要:
注意事项:
java.util.ServiceLoader使用线程的ClassLoader上下文Thread.currentThread()。setContextClassLoader(specialloader);
java.sql.DriverManager确实尊重了调用类&#39; ClassLoader, - 不是Thread的ClassLoader。使用Class.forName直接创建驱动程序(&#34; drivername&#34;,true,new URLClassLoader(urlarrayofextrajarsordirs).newInstance();
javax.activation使用线程的ClassLoader上下文(对javax.mail很重要)。
答案 3 :(得分:1)
我得到了一个运行在Java 8中的spring boot应用程序。我有任务将其升级到Java 11版本。
面临的问题:
由以下原因导致:java.lang.ClassCastException:jdk.internal.loader.ClassLoaders $ AppClassLoader(在模块:java.base中)不能转换为java.net.URLClassLoader(在模块:java.base中)
使用方法:
创建课程:
import java.net.URL;
/**
* This class has been created to make the code compatible after migration to Java 11
* From the JDK 9 release notes: "The application class loader is no longer an instance of
* java.net.URLClassLoader (an implementation detail that was never specified in previous releases).
* Code that assumes that ClassLoader.getSytemClassLoader() returns a URLClassLoader object will
* need to be updated. Note that Java SE and the JDK do not provide an API for applications or
* libraries to dynamically augment the class path at run-time."
*/
public class ClassLoaderConfig {
private final MockClassLoader classLoader;
ClassLoaderConfig() {
this.classLoader = new MockClassLoader(new URL[0], this.getClass().getClassLoader());
}
public MockClassLoader getClassLoader() {
return this.classLoader;
}
}
创建另一个课程:
import java.net.URL;
import java.net.URLClassLoader;
public class MockClassLoader extends URLClassLoader {
public MockClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
现在在您的主类的当前线程中设置它(在应用程序的开头)
Thread.currentThread().setContextClassLoader(new ClassLoaderConfig().getClassLoader());
希望此解决方案适用于您!!!
答案 4 :(得分:0)
例如,如果您只是想读取当前的类路径,因为您想启动另一个具有与当前类路径相同的类路径的JVM,则可以执行以下操作:
object ClassloaderHelper {
def getURLs(classloader: ClassLoader) = {
// jdk9+ need to use reflection
val clazz = classloader.getClass
val field = clazz.getDeclaredField("ucp")
field.setAccessible(true)
val value = field.get(classloader)
value.asInstanceOf[URLClassPath].getURLs
}
}
val classpath =
(
// jdk8
// ClassLoader.getSystemClassLoader.asInstanceOf[URLClassLoader].getURLs ++
// getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs
// jdk9+
ClassloaderHelper.getURLs(ClassLoader.getSystemClassLoader) ++
ClassloaderHelper.getURLs(getClass.getClassLoader)
)
默认情况下,无法通过反射访问$ AppClassLoader类中的最终字段,需要将额外的标志传递给JVM:
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED
答案 5 :(得分:0)
我找到了,并为我工作。
String pathSeparator = Syste .getProperty(“ path.separator”); String [] classPathEntries = System.getProperty(“ java.class.path”).split(pathSeparator);
来自网站https://blog.codefx.org/java/java-11-migration-guide/#Casting-To-URL-Class-Loader
答案 6 :(得分:0)
请参阅Edi的解决方案,这对我有用:
public final class IndependentClassLoader extends URLClassLoader {
private static final ClassLoader INSTANCE = new IndependentClassLoader();
/**
* @return instance
*/
public static ClassLoader getInstance() {
return INSTANCE;
}
private IndependentClassLoader() {
super(getAppClassLoaderUrls(), null);
}
private static URL[] getAppClassLoaderUrls() {
return getURLs(IndependentClassLoader.class.getClassLoader());
}
private static URL[] getURLs(ClassLoader classLoader) {
Class<?> clazz = classLoader.getClass();
try {
Field field = null;
field = clazz.getDeclaredField("ucp");
field.setAccessible(true);
Object urlClassPath = field.get(classLoader);
Method method = urlClassPath.getClass().getDeclaredMethod("getURLs", new Class[] {});
method.setAccessible(true);
URL[] urls = (URL[]) method.invoke(urlClassPath, new Object[] {});
return urls;
} catch (Exception e) {
throw new NestableRuntimeException(e);
}
}
}
在Eclipse中运行,您需要将VM参数设置为JUnit启动/调试配置。 通过命令行运行maven有两个选择:
选项1
将以下行添加到pom.xml:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<argLine>--add-opens java.base/jdk.internal.loader=ALL-UNNAMED</argLine>
</configuration>
</plugin>
选项2
运行mvn test -DargLine="-Dsystem.test.property=--add-opens java.base/jdk.internal.loader=ALL-UNNAMED"
答案 7 :(得分:-1)
也有这篇文章对我有所帮助。 我找不到该文章,但...在这里:https://github.com/CGJennings/jar-loader
这里是指南的一部分,发布时有一个罐子,您可以阅读他的指南并进行设置。
我自己尝试过下载包含类文件的jar文件
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;
public final class classname{
public static void premain(String agentArgs, Instrumentation instrumentation) {
loadedViaPreMain = true;
agentmain(agentArgs,instrumentation);
}
public final static void addToClassPath(File jarfile)throws IOException{inst.appendToSystemClassLoaderSearch(new JarFile(jarfile));}
public final static void agentmain(String agentArgs, Instrumentation instrumentation) {
if (instrumentation == null){throw new NullPointerException("instrumentation");}
if (inst == null) {inst = instrumentation;}
}
private static Instrumentation inst;
private static boolean loadedViaPreMain = false;
}
我只是自己尝试将这些代码打包为一个包,然后使用-javaagent:plugin ...... jar选项启动应用程序类,然后调用此函数。它不会更改我的类路径。我可能会丢失一些细节。
希望您可以使其正常工作。