How to load JAR files dynamically at Runtime? - java

Why is it so hard to do this in Java? If you want to have any kind of module system you need to be able to load JAR files dynamically. I'm told there's a way of doing it by writing your own ClassLoader, but that's a lot of work for something that should (in my mind at least) be as easy as calling a method with a JAR file as its argument.
Any suggestions for simple code that does this?

The reason it's hard is security. Classloaders are meant to be immutable; you shouldn't be able to willy-nilly add classes to it at runtime. I'm actually very surprised that works with the system classloader. Here's how you do it making your own child classloader:
URLClassLoader child = new URLClassLoader(
new URL[] {myJar.toURI().toURL()},
this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);
Painful, but there it is.

The following solution is hackish, as it uses reflection to bypass encapsulation, but it works flawlessly:
File file = ...
URL url = file.toURI().toURL();
URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);

You should take a look at OSGi, e.g. implemented in the Eclipse Platform. It does exactly that. You can install, uninstall, start and stop so called bundles, which are effectively JAR files. But it does a little more, as it offers e.g. services that can be dynamically discovered in JAR files at runtime.
Or see the specification for the Java Module System.

While most solutions listed here are either hacks (pre JDK 9) hard to configure (agents) or just don't work anymore (post JDK 9) I find it really surprising that nobody mentioned a clearly documented method.
You can create a custom system class loader and then you're free to do whatever you wish. No reflection required and all classes share the same classloader.
When starting the JVM add this flag:
java -Djava.system.class.loader=com.example.MyCustomClassLoader
The classloader must have a constructor accepting a classloader, which must be set as its parent. The constructor will be called on JVM startup and the real system classloader will be passed, the main class will be loaded by the custom loader.
To add jars just call ClassLoader.getSystemClassLoader() and cast it to your class.
Check out this implementation for a carefully crafted classloader. Please note, you can change the add() method to public.

How about the JCL class loader framework? I have to admit, I haven't used it, but it looks promising.
Usage example:
JarClassLoader jcl = new JarClassLoader();
jcl.add("myjar.jar"); // Load jar file
jcl.add(new URL("http://myserver.com/myjar.jar")); // Load jar from a URL
jcl.add(new FileInputStream("myotherjar.jar")); // Load jar file from stream
jcl.add("myclassfolder/"); // Load class folder
jcl.add("myjarlib/"); // Recursively load all jar files in the folder/sub-folder(s)
JclObjectFactory factory = JclObjectFactory.getInstance();
// Create object of loaded class
Object obj = factory.create(jcl, "mypackage.MyClass");

Here is a version that is not deprecated. I modified the original to remove the deprecated functionality.
/**************************************************************************************************
* Copyright (c) 2004, Federal University of So Carlos *
* *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without modification, are permitted *
* provided that the following conditions are met: *
* *
* * Redistributions of source code must retain the above copyright notice, this list of *
* conditions and the following disclaimer. *
* * Redistributions in binary form must reproduce the above copyright notice, this list of *
* * conditions and the following disclaimer in the documentation and/or other materials *
* * provided with the distribution. *
* * Neither the name of the Federal University of So Carlos nor the names of its *
* * contributors may be used to endorse or promote products derived from this software *
* * without specific prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT *
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR *
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR *
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
**************************************************************************************************/
/*
* Created on Oct 6, 2004
*/
package tools;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
/**
* Useful class for dynamically changing the classpath, adding classes during runtime.
*/
public class ClasspathHacker {
/**
* Parameters of the method to add an URL to the System classes.
*/
private static final Class<?>[] parameters = new Class[]{URL.class};
/**
* Adds a file to the classpath.
* #param s a String pointing to the file
* #throws IOException
*/
public static void addFile(String s) throws IOException {
File f = new File(s);
addFile(f);
}
/**
* Adds a file to the classpath
* #param f the file to be added
* #throws IOException
*/
public static void addFile(File f) throws IOException {
addURL(f.toURI().toURL());
}
/**
* Adds the content pointed by the URL to the classpath.
* #param u the URL pointing to the content to be added
* #throws IOException
*/
public static void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Class<?> sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL",parameters);
method.setAccessible(true);
method.invoke(sysloader,new Object[]{ u });
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}
}
public static void main(String args[]) throws IOException, SecurityException, ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{
addFile("C:\\dynamicloading.jar");
Constructor<?> cs = ClassLoader.getSystemClassLoader().loadClass("test.DymamicLoadingTest").getConstructor(String.class);
DymamicLoadingTest instance = (DymamicLoadingTest)cs.newInstance();
instance.test();
}
}

With Java 9, the answers with URLClassLoader now give an error like:
java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader
This is because the class loaders used have changed. Instead, to add to the system class loader, you can use the Instrumentation API through an agent.
Create an agent class:
package ClassPathAgent;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;
public class ClassPathAgent {
public static void agentmain(String args, Instrumentation instrumentation) throws IOException {
instrumentation.appendToSystemClassLoaderSearch(new JarFile(args));
}
}
Add META-INF/MANIFEST.MF and put it in a JAR file with the agent class:
Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent
Run the agent:
This uses the byte-buddy-agent library to add the agent to the running JVM:
import java.io.File;
import net.bytebuddy.agent.ByteBuddyAgent;
public class ClassPathUtil {
private static File AGENT_JAR = new File("/path/to/agent.jar");
public static void addJarToClassPath(File jarFile) {
ByteBuddyAgent.attach(AGENT_JAR, String.valueOf(ProcessHandle.current().pid()), jarFile.getPath());
}
}

Here is a quick workaround for Allain's method to make it compatible with newer versions of Java:
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, new File(jarPath).toURI().toURL());
} catch (NoSuchMethodException e) {
Method method = classLoader.getClass()
.getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
method.setAccessible(true);
method.invoke(classLoader, jarPath);
}
Note that it relies on knowledge of internal implementation of specific JVM, so it's not ideal and it's not a universal solution. But it's a quick and easy workaround if you know that you are going to use standard OpenJDK or Oracle JVM. It might also break at some point in future when new JVM version is released, so you need to keep that in mind.

The best I've found is org.apache.xbean.classloader.JarFileClassLoader which is part of the XBean project.
Here's a short method I've used in the past, to create a class loader from all the lib files in a specific directory
public void initialize(String libDir) throws Exception {
File dependencyDirectory = new File(libDir);
File[] files = dependencyDirectory.listFiles();
ArrayList<URL> urls = new ArrayList<URL>();
for (int i = 0; i < files.length; i++) {
if (files[i].getName().endsWith(".jar")) {
urls.add(files[i].toURL());
//urls.add(files[i].toURI().toURL());
}
}
classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(),
urls.toArray(new URL[urls.size()]),
GFClassLoader.class.getClassLoader());
}
Then to use the classloader, just do:
classLoader.loadClass(name);

If you are working on Android, the following code works:
String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");

I know I'm late to the party, but I have been using pf4j, which is a plug-in framework, and it works pretty well.
PF4J is a microframework and the aim is to keep the core simple but extensible.
An example of plugin usage:
Define an extension point in your application/plugin using ExtensionPoint interface marker:
public interface Greeting extends ExtensionPoint {
String getGreeting();
}
Create an extension using #Extension annotation:
#Extension
public class WelcomeGreeting implements Greeting {
public String getGreeting() {
return "Welcome";
}
}
Then you can load and unload the plugin as you wish:
public static void main(String[] args) {
// create the plugin manager
PluginManager pluginManager = new JarPluginManager(); // or "new ZipPluginManager() / new DefaultPluginManager()"
// start and load all plugins of application
pluginManager.loadPlugins();
pluginManager.startPlugins();
// retrieve all extensions for "Greeting" extension point
List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
for (Greeting greeting : greetings) {
System.out.println(">>> " + greeting.getGreeting());
}
// stop and unload all plugins
pluginManager.stopPlugins();
pluginManager.unloadPlugins();
}
For further details please refer to the documentation

Another working solution using Instrumentation that works for me. It has the advantage of modifying the class loader search, avoiding problems on class visibility for dependent classes:
Create an Agent Class
For this example, it has to be on the same jar invoked by the command line:
package agent;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;
public class Agent {
public static Instrumentation instrumentation;
public static void premain(String args, Instrumentation instrumentation) {
Agent.instrumentation = instrumentation;
}
public static void agentmain(String args, Instrumentation instrumentation) {
Agent.instrumentation = instrumentation;
}
public static void appendJarFile(JarFile file) throws IOException {
if (instrumentation != null) {
instrumentation.appendToSystemClassLoaderSearch(file);
}
}
}
Modify the MANIFEST.MF
Adding the reference to the agent:
Launcher-Agent-Class: agent.Agent
Agent-Class: agent.Agent
Premain-Class: agent.Agent
I actually use Netbeans, so this post helps on how to change the manifest.mf
Running
The Launcher-Agent-Class is only supported on JDK 9+ and is responsible for loading the agent without explicitly defining it on the command line:
java -jar <your jar>
The way that works on JDK 6+ is defining the -javaagent argument:
java -javaagent:<your jar> -jar <your jar>
Adding new Jar at Runtime
You can then add jar as necessary using the following command:
Agent.appendJarFile(new JarFile(<your file>));
I did not find any problems using this on documentation.

The solution proposed by jodonnell is good but should be a little bit enhanced. I used this post to develop my application with success.
Assign the current thread
Firstly we have to add
Thread.currentThread().setContextClassLoader(classLoader);
or you will not able to load resource (such as spring/context.xml) stored into the jar.
Do not include
your jars into the parent class loader or you will not able to understand who is loading what.
see also Problem reloading a jar using URLClassLoader
However, OSGi framework remain the best way.

Another version of the hackish solution from Allain, that also works on JDK 11:
File file = ...
URL url = file.toURI().toURL();
URLClassLoader sysLoader = new URLClassLoader(new URL[0]);
Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});
On JDK 11 it gives some deprecation warnings but serves as a temporary solution those who use Allain solution on JDK 11.

In case anyone searches for this in the future, this way works for me with OpenJDK 13.0.2.
I have many classes that I need to instantiate dynamically at runtime, each potentially with a different classpath.
In this code, I already have an object called pack, that holds some metadata about the class I am trying to load. The getObjectFile() method returns the location of the class file for the class. The getObjectRootPath() method returns the path to the bin/ directory containing the class files that include the class I am trying to instantiate. The getLibPath() method returns the path to a directory containing the jar files constituting the classpath for the module the class is a part of.
File object = new File(pack.getObjectFile()).getAbsoluteFile();
Object packObject;
try {
URLClassLoader classloader;
List<URL> classpath = new ArrayList<>();
classpath.add(new File(pack.getObjectRootPath()).toURI().toURL());
for (File jar : FileUtils.listFiles(new File(pack.getLibPath()), new String[] {"jar"}, true)) {
classpath.add(jar.toURI().toURL());
}
classloader = new URLClassLoader(classpath.toArray(new URL[] {}));
Class<?> clazz = classloader.loadClass(object.getName());
packObject = clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
throw e;
}
return packObject;
I was using the Maven dependency: org.xeustechnologies:jcl-core:2.8 to do this before, but after moving past JDK 1.8, it sometimes froze and never returned being stuck "waiting for references" at Reference::waitForReferencePendingList().
I am also keeping a map of class loaders so that they can be reused if the class I am trying to instantiate is in the same module as a class that I have already instantiated, which I would recommend.

please take a look at this project that i started: proxy-object lib
This lib will load jar from file system or any other location. It will dedicate a class loader for the jar to make sure there are no library conflicts. Users will be able to create any object from the loaded jar and call any method on it.
This lib was designed to load jars compiled in Java 8 from the code base that supports Java 7.
To create an object:
File libDir = new File("path/to/jar");
ProxyCallerInterface caller = ObjectBuilder.builder()
.setClassName("net.proxy.lib.test.LibClass")
.setArtifact(DirArtifact.builder()
.withClazz(ObjectBuilderTest.class)
.withVersionInfo(newVersionInfo(libDir))
.build())
.build();
String version = caller.call("getLibVersion").asString();
ObjectBuilder supports factory methods, calling static functions, and call back interface implementations.
i will be posting more examples on the readme page.

This can be a late response, I can do it as this (a simple example for fastutil-8.2.2.jar) using jhplot.Web class from DataMelt (http://jwork.org/dmelt)
import jhplot.Web;
Web.load("http://central.maven.org/maven2/it/unimi/dsi/fastutil/8.2.2/fastutil-8.2.2.jar"); // now you can start using this library
According to the documentation, this file will be download inside "lib/user" and then dynamically loaded, so you can start immediately using classes from this jar file in the same program.

I needed to load a jar file at runtime for both java 8 and java 9+ (above comments don't work for both of these versions). Here is the method to do it (using Spring Boot 1.5.2 if it may relate).
public static synchronized void loadLibrary(java.io.File jar) {
try {
java.net.URL url = jar.toURI().toURL();
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(Thread.currentThread().getContextClassLoader(), new Object[]{url});
} catch (Exception ex) {
throw new RuntimeException("Cannot load library from jar file '" + jar.getAbsolutePath() + "'. Reason: " + ex.getMessage());
}
}

For dynamic uploading of jar files, you can use my modification of URLClassLoader. This modification has no problem with changing the jar file during application operation, like the standard URLClassloader. All loaded jar files are loaded into RAM and thus independent of the original file.
In-memory jar and JDBC class loader

I personally find that java.util.ServiceLoader does the job pretty well. You can get an example here.

Related

Java 9 dynamically load a jar at runtime expanding Class-Path [duplicate]

Until java9 for adding external jar to classpath in runtime by programmatically everybody used:
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
method.invoke(sysloader, new Object[]{file.toURI().toURL()});
Now with java9 we have problem:
Exception in thread "main" java.lang.ClassCastException:
java.base/jdk.internal.loader.ClassLoaders$AppClassLoader
cannot be cast to java.base/java.net.URLClassLoader
URLClassLoader doesn't work anymore in Java 9. What to do now under jdk9 for adding an external jar to the classpath in runtime programmatically?
The JavaSE9 release notes read about the same :
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.
Additionally when an extended classpath is required, one can make use of
Class<?> clazz = Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));
as suggested in this thread from Oracle. This comes with caveats:
java.util.ServiceLoader uses the thread's ClassLoader context Thread.currentThread().setContextClassLoader(specialloader);
java.sql.DriverManager does honors the calling class' ClassLoader, -not- the Thread's ClassLoader. Create Driver directly
using Class.forName("drivername", true, new
URLClassLoader(urlarrayofextrajarsordirs).newInstance();
javax.activation uses the thread's ClassLoader context (important for javax.mail).
Naman's answer is not a correct replacement for what you are looking for.
The correct way to add a jar to the classpath in Java 9 and above is to use Java Instrumentation's appendToSystemClassLoaderSearch(JarFile jarfile) method.
First you will need to add your Agent class to your MANIFEST.MF
Launcher-Agent-Class: com.yourpackage.Agent
Then add your agent.
The example below will allow you to call Agent.addClassPath(File f) to add a Jar to the classpath in both Java 8 & 9+
public class Agent {
private static Instrumentation inst = null;
// The JRE will call method before launching your main()
public static void agentmain(final String a, final Instrumentation inst) {
Agent.inst = inst;
}
public static boolean addClassPath(File f) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
try {
// If Java 9 or higher use Instrumentation
if (!(cl instanceof URLClassLoader)) {
inst.appendToSystemClassLoaderSearch(new JarFile(f));
return;
}
// If Java 8 or below fallback to old method
Method m = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
m.setAccessible(true);
m.invoke(cl, (Object)f.toURI().toURL());
} catch (Throwable e) { e.printStackTrace(); }
}
}

Can't control logging levels when using jetty-runner

I have a Java 8 Maven webapp project that I am running using jetty-runner.jar. Everything works fine, except I can't control the logging levels (INFO, WARNING, FINE, FINER etc.). Am using java.util.logging and OS is Win7.
I've tried the following:
Created a logging.properties file in src/main/resources folder (ends up in WEB-INF/classes directory of the war).
Renamed logging.properties to jetty-logging.properties
Used -Djava.util.logging.config.file=WEB-INF/classes/logging.properties (when the file was so named) with the command to run jetty-runner
java -Djava.util.logging.config.file=WEB-INF/classes/logging.prop
erties -jar target/dependency/jetty-runner.jar target/xyz.war
Also tried -Djava.util.logging.config.file=/WEB-INF/classes/logging.properties (with a / in front of WEB-INF)
My logging file is simply:
.level = WARNING
I haven't had to mess around with logging much before (except GAE projects), so not sure what I am doing wrong.
Since you are trying to load the logging.properties from inside of the WAR file you have to use the java.util.logging.config.class. Per the documentation:
If the "java.util.logging.config.class" property is set, then the property value is treated as a class name. The given class will be loaded, an object will be instantiated, and that object's constructor is responsible for reading in the initial configuration. (That object may use other system properties to control its configuration.) The alternate configuration class can use readConfiguration(InputStream) to define properties in the LogManager.
Using that property you can use Class.getResourceAsStream to locate the file inside of the WAR file. Here is an example:
package foo.bar.baz;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
public class JulConfig {
/**
* Install this as the -Djava.util.logging.config.class=foo.bar.baz.JulConfig
* -Djava.util.logging.config.file=WEB-INF/classes/logging.properties
* #throws Exception if there is a problem.
*/
public JulConfig() throws Exception {
String key = "java.util.logging.config.file";
String file = System.getProperty(key, "logging.properties");
final InputStream in = JulConfig.class.getResourceAsStream(file);
if (in != null) {
try {
LogManager.getLogManager().readConfiguration(in);
//System.clearProperty(key); //Optional.
} finally {
try {
in.close();
} catch (IOException ignore) {
}
}
} else {
throw new FileNotFoundException(file);
}
}
}
The reason you have to do this is because the LogManager uses java.io.File to locate the logging.properties.

Why isn't my ResourceBundleControlProvider being loaded?

I thought I would use the new ResourceBundleControlProvider framework in Java 8 to fix something which Oracle themselves will never fix - the default encoding used when reading resource bundles.
So I made a control:
package com.acme.resources;
import java.io.IOException;
import java.util.Locale;
import java.util.ResourceBundle;
public class AcmeResourceBundleControl extends ResourceBundle.Control
{
#Override
public ResourceBundle newBundle(String baseName, Locale locale, String format,
ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException
{
throw new UnsupportedOperationException("TODO");
}
}
Then I made a provider:
package com.acme.resources;
import java.util.ResourceBundle;
import java.util.spi.ResourceBundleControlProvider;
public class AcmeResourceBundleControlProvider implements ResourceBundleControlProvider
{
private static final ResourceBundle.Control CONTROL = new AcmeResourceBundleControl();
#Override
public ResourceBundle.Control getControl(String baseName)
{
if (baseName.startsWith("com.acme."))
{
return CONTROL;
}
else
{
return null;
}
}
}
Then in META-INF/services/java.util.spi.ResourceBundleControlProvider:
com.acme.resources.AcmeResourceBundleControlProvider
Then I just tried to run our application from IDEA and I find that it never loads my provider (otherwise the exception would be raised.)
I have checked the names and they all seem to match up. I have checked the compiler output directory IDEA is using and it does contain the service file. I wrote a simple test program which just tries to look up the service:
public static void main(String[] args)
{
for (ResourceBundleControlProvider provider :
ServiceLoader.load(ResourceBundleControlProvider.class))
{
System.out.println(provider.getClass());
}
}
This does print out one entry which is the name of my implementation class. So the issue is not in the service file.
If I breakpoint inside ResourceBundle, I seem to be able to access the custom provider class. Initial forays into the debugger show that ServiceLoader isn't finding any implementations, but I can't figure out why. I'm sure there is some dodgy class loader magic going on which results in not loading my class. :(
Some scary documentation on the Javadoc makes it sound like it might have to be installed as a global extension. If that really is the case, it's a bit of a shame, because it seemed like a useful way to override the default (and in my opinion broken) behaviour. But I also read the tutorial on the matter and it didn't seem to be describing anything like that (unless the good behaviour was pulled out of Java 8 at the very last minute and the docs are out of date!)
The tutorial does state that the JAR containing the ResourceBundleControlProvider must be in the JVM's system extension directory. Section 6 of the tutorial describes the requirement:
java -Djava.ext.dirs=lib -cp build RBCPTest
When you install a Java extension, you typically put the JAR file of the extension in the lib/ext directory of your JRE. However, this command specifies the directory that contains Java extensions with the system property java.ext.dirs.
The JavaDoc for ServiceLoader.loadInstalled() also states that providers on the application's class path are ignored.
Your problem is that the java.util.ResourceBundle that comes with the JVM does a ServiceLoader.loadInstalled(ResourceBundleControlProvider.class) to obtain a list of providers in the static initializer, and uses the thus obtained list ever after.

Java: Automatic Custom ClassLoader

My application uses the Standard Widget Toolkit (SWT) for it's GUI. My problem is that the 32-bit SWT library does not work on a 64-bit JVM. But I don't want to make people select the correct architecture when getting the software. So, I want to bundle both the 32-bit and 64-bit libraries, and auto-detect the architecture during runtime. I found out I can get the correct architecture of the JVM like so:
if (System.getProperty("os.arch").contains("64")) {
// ...
}
Now all that's left is to load the jar. But the problem is, all the examples I found require that you manually load the class before using it.
Class.forName("MyClass", false, myClassLoader);
So my question is, is it possible to "register" my class loader, so that I don't have to load classes beforehand?
Update: I created my own child class of URLClassLoader and set it as the default class loader with the command line argument -Djava.system.class.loader; but I get this error:
Error occurred during initialization of VM
java.lang.Error: java.lang.NoSuchMethodException: com.program.LibraryLoader.<init>(java.lang.ClassLoader)
at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)
I think LibraryLoader.<init> refers to the constructor... but it's there (public LibraryLoader(URI[] urls)).
Update 2: Almost there, the JVM runs now. I added this constructor to make it work:
public LibraryLoader(ClassLoader classLoader) {
super(new URL[0], classLoader);
}
But after adding the jars with addPath() (file:lib/jars/swt.jar), it only produces a NoClassDefFoundError. Yes, I double-checked that the file exists.
You could try to inject your custom class loader by means of the "java.system.class.loader" property (see ClassLoader#getSystemClassLoader). However, I'd recommend to use OSGi and let the framework do the complicated stuff.
As part of the constructor for your custom ClassLoader, call definePackage with the appropriate information, with the URL pointing to the desired jar file.
This example shows that the custom class loader is called when I try to instantiate a class from swing, because I defined my class loader as the loader of that package.
import java.net.URL;
public class junk extends ClassLoader {
byte[] dummy = new byte[0];
public static void main(String[] args) throws Exception {
new junk();
new javax.swing.JPanel();
}
public junk() throws Exception {
definePackage("javax.swing","","","","","","",new URL("file://junk.class"));
}
public Class<?> findClass(String s) throws java.lang.ClassNotFoundException{
Class<?> retVal = super.findClass(s);
System.out.println("delegated responsibility for "+s+" to superclass");
return retVal;
}
public Package getPackage(String s) {
Package retVal = super.getPackage(s);
System.out.println("delegated responsibility for "+s+" to superclass");
return retVal;
}
}
Result:
delegated responsibility for javax.swing to superclass

Why can't System.setProperty() change the classpath at runtime?

I am refering to the question on changing the classpath programmatically.
I read and found out that there is some function under System class as getproperties where we can retrieve the properties and then also can set it using setProperties().
The answers however I got was that It Wont work. I have not tried this myself, however, i am taking the call.
Just to clarify, then why these setProperty() and getProperty() methods are there if they cannot alter it at run time. Or is this specific to the classpath property only ?
I will appreciate if someone can present a scenario where they are really helpful?
You can certainly set any system properties you want at any point of time. The question is, will it have any effect? In the case of classpath, the answer is NO. The system class loader is initialized at a very early point in the startup sequence. It copies the classpath into its own data structures, and the classpath property is not read again. Changing it affect nothing in the system.
The reason for this may be two-fold. The lesser reason is performance. You may need to have some sort of data structure built for quick lookup of resources, and re-parsing classpath every time may be inefficient. The more important reason is security. You don't want a rogue class change the classpath under you and load compromised version of another class.
Modify Classpath
Even though you cannot set the classpath using the system properties (because the JVM reads system properties once: at startup), you can still change the classpath by forcibly invoking the addURL method of the classloader. Note that the solution below does not take into consideration the current thread. Consequently, it might not be accurate in all situations.
Example Solution
The original source on Sun's website for the following code has been removed:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
/**
* Allows programs to modify the classpath during runtime.
*/
public class ClassPathUpdater {
/** Used to find the method signature. */
private static final Class[] PARAMETERS = new Class[]{ URL.class };
/** Class containing the private addURL method. */
private static final Class<?> CLASS_LOADER = URLClassLoader.class;
/**
* Adds a new path to the classloader. If the given string points to a file,
* then that file's parent file (i.e., directory) is used as the
* directory to add to the classpath. If the given string represents a
* directory, then the directory is directly added to the classpath.
*
* #param s The directory to add to the classpath (or a file, which
* will relegate to its directory).
*/
public static void add( String s )
throws IOException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
add( new File( s ) );
}
/**
* Adds a new path to the classloader. If the given file object is
* a file, then its parent file (i.e., directory) is used as the directory
* to add to the classpath. If the given string represents a directory,
* then the directory it represents is added.
*
* #param f The directory (or enclosing directory if a file) to add to the
* classpath.
*/
public static void add( File f )
throws IOException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
f = f.isDirectory() ? f : f.getParentFile();
add( f.toURI().toURL() );
}
/**
* Adds a new path to the classloader. The class must point to a directory,
* not a file.
*
* #param url The path to include when searching the classpath.
*/
public static void add( URL url )
throws IOException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
Method method = CLASS_LOADER.getDeclaredMethod( "addURL", PARAMETERS );
method.setAccessible( true );
method.invoke( getClassLoader(), new Object[]{ url } );
}
private static URLClassLoader getClassLoader() {
return (URLClassLoader)ClassLoader.getSystemClassLoader();
}
}
The link no longer works: http://forums.sun.com/thread.jspa?threadID=300557
Example Usage
The following example will add /home/user/dev/java/app/build/com/package to the classpath at runtime:
try {
ClassPathUpdater.add( "/home/user/dev/java/app/build/com/package/Filename.class" );
}
catch( Exception e ) {
e.printStackTrace();
}
System.setProperty can be used to set some security or protocol handler at the beginning of a program. Like:
/*
Add the URL handler to the handler property. This informs
IBMJSSE what URL handler to use to handle the safkeyring
support. In this case IBMJCE.
*/
System.setProperty("java.protocol.handler.pkgs", "com.ibm.crypto.provider");
or for using SSL:
System.setProperty("javax.net.ssl.keyStore", context.getRealPath(KEYSTORE));
System.setProperty("javax.net.ssl.keyStorePassword", "password");
System.setProperty("javax.net.ssl.trustStore", context.getRealPath(TRUSTSTORE));
System.setProperty("javax.net.debug", "ssl");
HttpClient httpClient = new HttpClient();
GetMethod httpGet = new GetMethod("https://something.com");
httpClient.executeMethod(httpGet);
return new String(httpGet.getResponseBody());
But beware, because it changes the environment at runtime for ALL applications running in the same jvm.
If for example one application needs to run with saxon and the other with xalan and both make use of System.setProperty to set the transformerFactory, then you will run into trouble
As said in Monitored System.setProperty article,
System.setProperty() can be an evil call.
It is 100% thread-hostile
It contains super-global variables
It is extremely difficult to debug when these variables mysteriously change at runtime
Regarding the classpath property, as I said in a previous question, it can not be easily changed as runtime.
In particular, java System property java.class.path is used to build a linked link when the JRE is instantiated, then is not re-read. Therefore, changes you make to the property don't really do anything to the existing virtual machine.
There is also a way to change java.library.path in runtime, to do that, just do:
System.setProperty( "java.library.path", newPath);
Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
fieldSysPath.setAccessible(true);
fieldSysPath.set(null, null); // that's the key.
When this private static field in ClassLoader class is set to null, on next attempt to load native library ClassLoader will be initialized again using the new value in java.library.path.
The basic idea of getProperty() is that programs/code can be configured from outside of the JVM, passing properties on the command line using the java -Dfoo=bar syntax.
As you may want to configure certain behaviour in other software components (such as a logging component) in situations where you don't have control over the command line - think being deployed in a Servlet container - setProperty() comes in as a handy way to programmatically alter settings, e.g., before instantiating your logging utility.
The problem that is exhibited by the classpath issue is that programs will typically only read such system properties exactly once, when they are first initialized. So changing the classpath after JVM startup doesn't change anything for you app itself, because the JVM is already initialized, and changing some logging configuration after you have already obtained a Logger instance (or whatever), typically won't have any effect either.

Categories

Resources