Le code suivant ajoute un fichier jar au chemin de génération, il fonctionne très bien avec Java 8. Cependant, il lève une exception avec Java 9, l'exception est liée à le cast vers URLClassLoader. Toutes les idées comment cela peut être résolu? une solution optimale le modifiera pour fonctionner avec les deux 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;
}
Vous avez rencontré le fait que le chargeur de classe système n'est plus un URLClassLoader
. Comme indiqué par le type de retour de ClassLoader::getSystemClassLoader
, Il s'agissait d'un détail d'implémentation, même si une quantité non négligeable de code était utilisée.
À en juger par les commentaires, vous cherchez un moyen de charger dynamiquement des classes au moment de l'exécution. Comme souligne Alan Bateman , cela ne peut pas être fait dans Java 9 en ajoutant au chemin de classe.
Vous devriez plutôt envisager de créer un nouveau chargeur de classe pour cela. Cela a l'avantage supplémentaire de vous permettre de vous débarrasser des nouvelles classes car elles ne sont pas chargées dans le chargeur de classe d'application. Si vous compilez avec Java 9, vous devriez lire sur couches - ils vous donnent une abstraction propre pour charger un graphique de module entièrement nouveau.
J'ai trébuché sur cette question il y a quelque temps. Comme beaucoup, j'avais utilisé une méthode similaire à celle de la question
private static int AddtoBuildPath(File f)
pour ajouter dynamiquement des chemins au chemin de classe lors de l'exécution. Le code dans la question est probablement un mauvais style à plusieurs égards: 1) en supposant que ClassLoader.getSystemClassLoader()
renvoie un URLClassLoader
est un détail d'implémentation non documenté et 2) en utilisant la réflexion pour faire addURL
le public en est peut-être un autre.
façon plus propre d'ajouter dynamiquement des chemins de classe
Dans le cas où vous devez utiliser les URL de chemin de classe supplémentaires pour le chargement de classe via "Class.forName
", Une solution propre, élégante et compatible (Java 8 à 10) est la suivante:
1) Écrivez votre propre chargeur de classe en étendant le chargeur de classe URL, en ayant une méthode publique addURL
public class MyClassloader extends URLClassLoader {
public MyClassloader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
2) Déclarez un objet (à l'échelle de singleton/application) de votre chargeur de classe
private final MyClassloader classLoader;
et l'instancier via
classLoader = new MyClassloader(new URL[0], this.getClass().getClassLoader());
Remarque: Le chargeur de classe système est le parent. Les classes chargées par classLoader
connaissent celles qui peuvent être chargées par this.getClass().getClassLoader()
mais pas l'inverse.
3) Ajoutez des chemins de classe supplémentaires chaque fois que nécessaire (dynamiquement):
File file = new File(path);
if(file.exists()) {
URL url = file.toURI().toURL();
classLoader.addURL(url);
}
4) Instanciez des objets ou votre application via votre chargeur de classe singleton via
cls = Class.forName(name, true, classLoader);
Remarque: Étant donné que les chargeurs de classe essaient une délégation vers le chargeur de classe parent avant de charger une classe (et le parent vers son parent), vous devez vous assurer que la classe à charger n'est pas visible par le chargeur de classe parent pour vous assurer qu'elle est chargé via le chargeur de classe donné. Pour rendre cela plus clair: si vous avez ClassPathB
sur votre chemin d'accès aux classes système et ajoutez plus tard ClassPathB
et un peu ClassPathA
à votre classLoader
personnalisé, alors les classes sous ClassPathB
sera chargé via le chargeur de classe système et les classes sous ClassPathA
ne leur sont pas connues. Cependant, si vous supprimez ClassPathB
de votre chemin d'accès aux classes système, ces classes seront chargées via votre classLoader
personnalisé, puis les classes sous ClassPathA sont connues de celles sous ClassPathB.
5) Vous pouvez envisager de passer votre chargeur de classe à un thread via
setContextClassLoader(classLoader)
dans le cas où ce thread utilise getContextClassLoader
.
Shadov a pointé un fil sur communauté Oracle . Il y a la bonne réponse:
Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));
Les mises en garde qui y sont mentionnées sont également importantes:
Avertissements:
Java.util.ServiceLoader utilise le contexte ClassLoader du thread Thread.currentThread (). SetContextClassLoader (specialloader);
Java.sql.DriverManager honore la classe appelante 'ClassLoader, -pas- le ClassLoader du thread. Créez directement le pilote à l'aide de Class.forName ("nom_direction", true, new URLClassLoader (urlarrayofextrajarsordirs) .newInstance ();
javax.activation utilise le contexte ClassLoader du thread (important pour javax.mail).
Si vous cherchez simplement à lire le chemin d'accès aux classes actuel, par exemple parce que vous souhaitez faire tourner une autre machine virtuelle Java avec le même chemin d'accès aux classes que l'actuel, vous pouvez effectuer les opérations suivantes:
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)
)
Par défaut, les champs finaux de la classe $ AppClassLoader ne peuvent pas être accédés via la réflexion, un indicateur supplémentaire doit être transmis à la JVM:
--add-opens Java.base/jdk.internal.loader=ALL-UNNAMED
j'ai trouvé cela et j'ai travaillé pour moi.
String pathSeparator = Syste .getProperty ("path.separator"); String [] classPathEntries = System.getProperty ("Java.class.path") .split (pathSeparator);
à partir du site Web https://blog.codefx.org/Java/java-11-migration-guide/#Casting-To-URL-Class-Loader
En ce qui concerne la solution d'Edi, cela a fonctionné pour moi:
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);
}
}
}
En cours d'exécution dans Eclipse, vous devez définir VM Arguments pour la configuration de lancement/débogage JUnit. En exécutant avec maven via la ligne de commande, vous avez deux options:
Option 1
Ajoutez les lignes suivantes à 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>
Option 2
courir mvn test -DargLine="-Dsystem.test.property=--add-opens Java.base/jdk.internal.loader=ALL-UNNAMED"