Create Classloader with only classes inside specific folder - java

I want to load specific jars in ScriptEngineManager using specific ClassLoader
This constructor loads the implementations of ScriptEngineFactory visible to the given ClassLoader using the service provider mechanism.
The problem when I tried to create Classloader
File file = new File("c:\\myclasses\\");
try {
// Convert File to a URL
URL url = file.toURI().toURL(); // file:/c:/myclasses/
URL[] urls = new URL[]{url};
// Create a new class loader with the directory
ClassLoader cl = new URLClassLoader(urls)
And I'm getting also class that weren't in class for example Spring
Class cls1 = cl.loadClass("org.springframework.http.HttpStatus");
How can I create a clean classloader with only specific folder's classes?
EDIT
If it's not possible, can I use in groovy script something equivalent to Rhino's ClassShutter
private static void setJavaClassesVisibleInvisibleSandbox(Context cx)
{
cx.setClassShutter(new ClassShutter()
{
public boolean visibleToScripts(String className)
{
// No Java classes allowed inside scripts
EDIT
When tried #Vinz243 CustomClassLoader solution failed to load classes for classes files or jar files from specific folder, even tried rt.jar
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at com.CustomClassLoader.loadClass(CustomClassLoader.java:15)

As specified in the javadoc :
public URLClassLoader(URL[] urls)
Constructs a new URLClassLoader for the specified URLs using the default delegation parent ClassLoader. The URLs will be searched in the order specified for classes and resources after first searching in the parent class loader. Any URL that ends with a '/' is assumed to refer to a directory. Otherwise, the URL is assumed to refer to a JAR file which will be downloaded and opened as needed.
So you can try to extend ClassLoader since the mechanism responsible for loading the parent class is inside java.lang.ClassLoader#loadClass(java.lang.String, boolean)
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
EDIT
Not tested, but something like that should do the job:
class CustomClassLoader extends URLClassLoader {
public CustomClassLoader (URL[] urls) throws NoSuchMethodException {
super(urls);
}
#Override
protected Class<?> loadClass (String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> aClass = findClass(name);
if (resolve) {
resolveClass(aClass);
}
return aClass;
}
}
}

Related

Java 9 - add jar dynamically at runtime

I've got a classloader problem with Java 9.
This code worked with previous Java versions:
private static void addNewURL(URL u) throws IOException {
final Class[] newParameters = new Class[]{URL.class};
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class newClass = URLClassLoader.class;
try {
Method method = newClass.getDeclaredMethod("addNewURL", newParameters );
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[]{u});
} catch (Throwable t) {
throw new IOException("Error, could not add URL to system classloader");
}
}
From this thread I learned that this has to be replaced by something like this:
Class.forName(classpath, true, loader);
loader = URLClassLoader.newInstance(
new URL[]{u},
MyClass.class.getClassLoader()
MyClass is the class I'm trying to implement the Class.forName() method in.
u = file:/C:/Users/SomeUser/Projects/MyTool/plugins/myNodes/myOwn-nodes-1.6.jar
String classpath = URLClassLoader.getSystemResource("plugins/myNodes/myOwn-nodes-1.6.jar").toString();
For some reason - I really can't figure out, why - I get a ClassNotFoundException when running Class.forName(classpath, true, loader);
Does someone know what I'm doing wrong?
From the documentation of the Class.forName(String name, boolean initialize, ClassLoader loader) :-
throws ClassNotFoundException - if the class cannot be located by the specified class loader
Also, note the arguments used for the API includes the name of the class using which the classloader returns the object of the class.
Given the fully qualified name for a class or interface (in the same format returned by getName) this method attempts to locate, load, and link the class or interface.
In your sample code, this can be redressed to something like :
// Constructing a URL form the path to JAR
URL u = new URL("file:/C:/Users/SomeUser/Projects/MyTool/plugins/myNodes/myOwn-nodes-1.6.jar");
// Creating an instance of URLClassloader using the above URL and parent classloader
ClassLoader loader = URLClassLoader.newInstance(new URL[]{u}, MyClass.class.getClassLoader());
// Returns the class object
Class<?> yourMainClass = Class.forName("MainClassOfJar", true, loader);
where MainClassOfJar in the above code shall be replaced by the main class of the JAR myOwn-nodes-1.6.jar.

How to load jars dynamically from different locations into current class loader?

Whenever I deploy a web app in tomcat, all the jars inside WEB-INF/lib will be loaded into application ClassLoader.
I have few other locations with some set/s of jars,like WEB-INF/ChildApp1/*.jar, WEB-INF/ChildApp2/*.jar.....
Based on user request I want to load some set of jars into current class loader.
Note : I don't want to create any child class loader.
My real requirement is, programmatically how to add jars into current class loader.
I did it once, but this is kinda hack. Please see the code below:
final URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
final Class<URLClassLoader> sysclass = URLClassLoader.class;
// TODO some kind of a hack. Need to invent better solution.
final Method method = sysclass.getDeclaredMethod("addURL", new Class[] { URL.class });
method.setAccessible(true);
for (final File jar : jars) {
method.invoke(sysloader, new URL[] { jar.toURI().toURL() });
}
You need to change ClassLoader.getSystemClassloader() to class loader you want to use. You also have to check if this is an instance of URLClassloader
I think there is better solution, but this worked for me
You will need to implement your own WebappClassLoaderBase to be defined in the configuration of the loader in the context.xml.
Implement your WebappClassLoaderBase
The simplest way is to extend WebappClassLoader as next
package my.package;
public class MyWebappClassLoader extends WebappClassLoader {
public MyWebappClassLoader() {
}
public MyWebappClassLoader(final ClassLoader parent) {
super(parent);
}
#Override
public void start() throws LifecycleException {
String[] paths = {"/WEB-INF/ChildApp1/lib", "/WEB-INF/ChildApp2/lib"};
// Iterate over all the non standard locations
for (String path : paths) {
// Get all the resources in the current location
WebResource[] jars = resources.listResources(path);
for (WebResource jar : jars) {
// Check if the resource is a jar file
if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
// Add the jar file to the list of URL defined in the parent class
addURL(jar.getURL());
}
}
}
// Call start on the parent class
super.start();
}
}
Deploy your WebappClassLoaderBase
Build your own WebappClassLoaderBase using the tomcat jar corresponding to your tomcat version that is available from here.
Create a jar from it
And put the jar in tomcat/lib in order to make it available from the Common ClassLoader
Configure your WebappClassLoaderBase
Define your WebappClassLoaderBase in the context.xml
<Context>
...
<Loader loaderClass="my.package.MyWebappClassLoader" />
</Context>
It is done, now your webapps will be able to load jar files from /WEB-INF/ChildApp1/lib and /WEB-INF/ChildApp2/lib.
Response Update
As you would like to do the same thing but with only a war, you will need to use a hack to add dynamically your jar files.
Here is how you can proceed:
Implement a ServletContextListener to add your jar files
In order to add your jar files dynamically when the context is initialized, you need to create a ServletContextListener that will call URLClassLoader#addURL(URL) by refection which is an ugly hack but it works. Note that it works because the ClassLoader of a webapp in Tomcat is a WebappClassLoader which is actually a subclass of URLClassLoader.
package my.package;
public class MyServletContextListener implements ServletContextListener {
#Override
public void contextInitialized(final ServletContextEvent sce) {
try {
// Get the method URLClassLoader#addURL(URL)
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
// Make it accessible as the method is protected
method.setAccessible(true);
String[] paths = {"/WEB-INF/ChildApp1/lib", "/WEB-INF/ChildApp2/lib"};
for (String path : paths) {
File parent = new File(sce.getServletContext().getRealPath(path));
File[] jars = parent.listFiles(
new FilenameFilter() {
#Override
public boolean accept(final File dir, final String name) {
return name.endsWith(".jar");
}
}
);
if (jars == null)
continue;
for (File jar : jars) {
// Add the URL to the context CL which is a URLClassLoader
// in case of Tomcat
method.invoke(
sce.getServletContext().getClassLoader(),
jar.toURI().toURL()
);
}
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
#Override
public void contextDestroyed(final ServletContextEvent sce) {
}
}
Declare your ServletContextListener
In the web.xml of your webapp simply add:
<listener>
<listener-class>my.package.MyServletContextListener</listener-class>
</listener>

java.lang.ClassNotFoundException while attempting to load dynamically a class in glassfish

I'm trying to load dynamically a class contained in a .jar file. I know the whole class name and I know for sure that the class implements the interface AlgorithmClass.
My code looks like this:
addURLToSystemClassLoader(dir.toURI().toURL());
Class cl = Class.forName(algorithm.getClassName());
AlgorithmClass algorithmClass = (AlgorithmClass)cl.newInstance();
Where dir is the File object of the .jar file and addURLToSystemClassLoader(URL) looks like this:
private void addURLToSystemClassLoader(URL url) throws IntrospectionException {
URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> classLoaderClass = URLClassLoader.class;
try {
Method method = classLoaderClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(systemClassLoader, new Object[]{url});
} catch (Throwable t) {
t.printStackTrace();
throw new IntrospectionException("Error when adding url to system ClassLoader ");
}
}
I checked and the URL is being added to the class loader.
When I try to get the Class object I get the error:
SEVERE: javax.servlet.ServletException: java.lang.ClassNotFoundException: id3.Algorithm
(id3.Algorithm is the full name of the class I'm trying to load)
I've tried creating a new ClassLoader like below:
ClassLoader cload = new URLClassLoader(new URL[]{dir.toURI().toURL()}, ClassLoader.getSystemClassLoader());
Class cl = Class.forName(algorithm.getClassName(), false, cload);
AlgorithmClass algorithmClass = (AlgorithmClass)cl.newInstance();
But then I get the error:
java.lang.NoClassDefFoundError: lib/algorithm/AlgorithmClass
I've tried creating a new URLClassLoader with all the URLs that the system class loader has but the effect was the same.
The "worst" part of this is that both ways are working perfectly fine on the jUnit test that I have for testing this part of my code.
I'm using Glassfish 3.1.1 as my app server.
dir shouldn't contain 'lib'.
Try this:
ClassLoader cload = new URLClassLoader(new URL[]{dir.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
Class cl = Class.forName(algorithm.getClassName(), true, cload);
AlgorithmClass algorithmClass = (AlgorithmClass)cl.newInstance();
You have class-loading issue. You shoud be aware that your addURLToSystemClassLoader() is actually the heck...
Put your jar to the classpath. Use Class.forName() idiom. If it fails use version that receives ClassLoader as parammeter namely
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
and path Thread.currentThread().getContextClassLoader() as ClassLoader parameter.
See also my another answer below.

Custom URLClassLoader, NoClassDefFoundError when run

I've created my own URLClassLoader, and set it as the system classloader via java.system.class.loader. It's initialized and everything, but the classes I'm trying to load aren't found. Here's the URLClassLoader:
public class LibraryLoader extends URLClassLoader
{
public LibraryLoader(ClassLoader classLoader)
{
super(new URL[0], classLoader);
}
synchronized public void addJarToClasspath(String jarName) throws MalformedURLException, ClassNotFoundException
{
File filePath = new File(jarName);
URI uriPath = filePath.toURI();
URL urlPath = uriPath.toURL();
System.out.println(filePath.exists());
System.out.println(urlPath.getFile());
addURL(urlPath);
}
}
I've confirmed that the jar exists, and that the path is correct. This is how I call it in my program:
LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
loader.addJarToClasspath("swt.jar");
This is the exception that I get (line 166 refers to the line at which I try to create a new Point:
Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/swt/graphics/Point
at mp.MyProgram.loadArchitectureLibraries(MyProgram.java:116)
at mp.MyProgram.main(MyProgram.java:90)
Caused by: java.lang.ClassNotFoundException: org.eclipse.swt.graphics.Point
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 2 more
I even tried explicitly loading the class like so:
Class.forName("org.eclipse.swt.graphics.Point", false, loader);
What might be causing this? Shouldn't it "just work"?
Update: Here's the important code from MyProgram
public class MyProgram
{
// ...
public static void main(String[] args)
{
loadArchitectureLibraries();
// ...
}
public static void loadArchitectureLibraries()
{
LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
String architecture = System.getProperty("os.arch");
try {
if (architecture.contains("64")) {
loader.addJarToClasspath("swt-3.6.1-win32-win32-x86_64.jar");
} else {
loader.addJarToClasspath("swt-3.6.1-win32-win32-x86.jar");
}
Class.forName("org.eclipse.swt.graphics.Point", false, loader);
org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
} catch (Exception exception) {
exception.printStackTrace();
System.out.println("Could not load SWT library");
System.exit(1);
}
}
}
Update 2: Here's an SSCCE: http://nucleussystems.com/files/myprogram.zip . Call java -Djava.system.class.loader=mp.LibraryLoader -jar myprogram.jar.
I would have to agree with the comments on this question. Based on the code you have provided, it would appear that you are getting the error due to the JAR files not being where you expect them to be. As mentioned by #Andrew, you are not checking the existence of the file in your addJarToClasspath method. As a result, if the file does not exist, you will receive a ClassNotFound exception as you are seeing. I verified this problem by taking your ClassLoader logic and passing it a valid and an invalid JAR. When a valid JAR/path was provided, the ClassLoader loaded the class as expected. When an invalid JAR/path was specified, I received the error you mentioned. The URLClassLoader does not throw an exception if an URL is specified that does not point to a valid file.
To validate the scenario, print out the path of the full path of your File and see if it is correct for the execution environment.
Edit
It appears that even if you override the system ClassLoader, the VM will still use the default sun.misc.Launcher$AppClassLoader to load some classes. In my testing this includes the classes that are referenced from the main application. I'm sure there is a reason for this process, however, I am unable to ascertain it at this time. I have come up with a few solutions for you:
Use a script to detect the environment and set the classpath accordingly. This is perhaps the simplest solution, but one you may or may not want to take based on your particular requirements.
Similar to what was mentioned in other answers, specifically load and execute your application using your custom ClassLoader. This does not mean creating a single class that will be loaded and then invoke your application. It means that any class that needs to interact with the dynamically loaded swt libraries and any classes that need to reference your application classes should be loaded from your custom ClassLoader. Any application dependencies, such as log4j, etc, can be referenced by the default application ClassLoader. Here is an example of how this would work:
JAR 1 (launcher.jar):
public class AppLauncher {
public static void main(String… args) throws Exception {
ClassLoader loader = initClassLoader();
Class<?> mpClass = loader.loadClass("mp.MyProgram");
// using Runnable as an example of how it can be done
Runnable mpClass = (Runnable) mpClass.newInstance();
}
public static ClassLoader initClassLoader() {
// assuming still configured as system classloader, could also be initialized directly
LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
// add the main application jar to the classpath.
// You may want to dynamically determine this value (lib folder) or pass it in as a parameter
loader.addJarToClasspath("myapp.jar");
String architecture = System.getProperty("os.arch");
try {
if (architecture.contains("64")) {
loader.addJarToClasspath("swt-3.6.1-win32-win32-x86_64.jar");
} else {
loader.addJarToClasspath("swt-3.6.1-win32-win32-x86.jar");
}
Class.forName("org.eclipse.swt.graphics.Point", false, loader);
org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
} catch (Exception exception) {
exception.printStackTrace();
System.out.println("Could not load SWT library");
System.exit(1);
}
return loader;
}
JAR 2 (myapp.jar): Includes all class which depend on swt
public class MyProgram implements Runnable {
//…
public void run() {
// perform application execution
// this logic should now work
org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0,0);
}
}
The AppLauncher class would be executed by the VM without the rest of your application being included in the execution Jar.
java -Djava.system.class.loader=test.LibraryLoader -cp <dependency jars>:launcher.jar mp.AppLauncher
I see that there have been updates to the other answers. Since I already had typed up the above comments, I figured that I should still post it for your perusal.
It's visible from a (few) mile(s) away you are not using the custom classloader beside Class.forName
The ClassNoDefFoundError occurs since the classloader that has loaded current class MyProgram attempts to load org.eclipse.swt.graphics.Point.
You need to load another class (call it launcher) via Class.forName and then start from there - implement some interface (even runnable will do) and call it.
edit
How to do it, a simplistic scenario.
1. Create another class called mp.loader.Launcher that implements Runnable like that.
public class Launcher implements Runnable{
public void run(){
org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
//whatever, start from here.
}
}
2. Place it in another jar called swt-loader.jar.
in MyProgram class use:
loader.addJarToClasspath("swt-loader.jar");
Runnable r = (Runnable) Class.forName("mp.loader.Launcher", true, loader);
r.run();//there you have
Since the offending line is not the Class.forName but the actual initialization of an instance of Point, we'll have to make sure that the class, that tries to load the Point class, was created by the Library class loader. Therefore, I made some minor changes in the LibraryLoader accordingt to this blog entry
public class LibraryLoader extends URLClassLoader {
public LibraryLoader(ClassLoader classLoader) {
super(new URL[0], classLoader);
}
synchronized public void addJarToClasspath(String jarName)
throws MalformedURLException, ClassNotFoundException {
File filePath = new File(jarName);
URI uriPath = filePath.toURI();
URL urlPath = uriPath.toURL();
System.out.println(filePath.exists());
System.out.println(urlPath.getFile());
addURL(urlPath);
}
#Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if ("mp.MyProgram".equals(name)) {
return getClass(name);
}
return super.loadClass(name, resolve);
}
private Class<?> getClass(String name) throws ClassNotFoundException {
String file = name.replace('.', File.separatorChar) + ".class";
byte[] b = null;
try {
b = loadClassData(file);
Class<?> c = defineClass(name, b, 0, b.length);
resolveClass(c);
return c;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private byte[] loadClassData(String name) throws IOException {
InputStream stream = getClass().getClassLoader().getResourceAsStream(
name);
int size = stream.available();
byte buff[] = new byte[size];
DataInputStream in = new DataInputStream(stream);
in.readFully(buff);
in.close();
return buff;
}
}
In the program itself, we have to extract a new method since all the classes, that are used from within a method, seem to be loaded up-front:
public class MyProgram {
public static void main(String[] args) {
LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
String architecture = System.getProperty("os.arch");
try {
loader.addJarToClasspath("swt.jar");
otherMethod();
} catch (Throwable exception) {
// println instead of logger because logging is useless at this level
exception.printStackTrace();
System.out.println("Could not load SWT library");
System.exit(1);
}
}
protected static void otherMethod() {
org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
System.out.println("Works!");
}
}
That should work for you.

How do I create a parent-last / child-first ClassLoader in Java, or How to override an old Xerces version that was already loaded in the parent CL?

I would like to create a parent-last / child-first class loader, e.g. a class loader that will look for classes in the child class loder first, and only then delegate to it's parent ClassLoader to search for classes.
Clarification:
I know now that to get complete ClassLoading seperation I need to use something like a URLClassLoader passing null as it's parent, thanks to this answer to my previous question
However the current question comes to help me resolve this issue:
My code + dependent jars are being loaded into an existing system, using a ClassLoader that sets that System's ClassLoader as it's parent (URLClassLoader)
That System uses some libraries of a version not compatible with the one I need (e.g. older version of Xerces, that doesn't allow me to run my code)
My code runs perfectly fine if runs stand alone, but it fails if runs from that ClassLoader
Howerver I do need access to many other classes within the parent ClassLoader
Therefore I want to allow me to Override, the parent classloader "jars" with my own: If a class I call is found in the child class loader (e.g. I provided a newer version of Xerces with my own jars, instead of the one users by the ClassLoader that loaded my code and jars.
Here is the System's code that loads my code + Jars (I can't change this one)
File addOnFolder = new File("/addOns");
URL url = addOnFolder.toURL();
URL[] urls = new URL[]{url};
ClassLoader parent = getClass().getClassLoader();
cl = URLClassLoader.newInstance(urls, parent);
Here is "my" code (taken fully from the Flying Sauser "Hello World" code demo):
package flyingsaucerpdf;
import java.io.*;
import com.lowagie.text.DocumentException;
import org.xhtmlrenderer.pdf.ITextRenderer;
public class FirstDoc {
public static void main(String[] args)
throws IOException, DocumentException {
String f = new File("sample.xhtml").getAbsolutePath();
System.out.println(f);
//if(true) return;
String inputFile = "sample.html";
String url = new File(inputFile).toURI().toURL().toString();
String outputFile = "firstdoc.pdf";
OutputStream os = new FileOutputStream(outputFile);
ITextRenderer renderer = new ITextRenderer();
renderer.setDocument(url);
renderer.layout();
renderer.createPDF(os);
os.close();
}
}
This works standalone (running main) but fails with this error when loaded through the parent CL:
org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to
create or change an object in a way
which is incorrect with regard to
namespaces.
probably because the parent system uses Xerces of an older version, and even though I provide the right Xerces jar in the /addOns folder, since it's classes were already loaded and used by the parent System, it doesn't allow my own code to use my own jar due to the direction of the delegation. I hope this makes my question clearer, and I'm sure it has been asked
before. (Perhaps I don't ask the right question)
Today is your lucky day, as I had to solve this exact problem. I warn you though, the innards of class loading are a scary place. Doing this makes me think that the designers of Java never imagined that you might want to have a parent-last classloader.
To use just supply a list of URLs containing classes or jars to be available in the child classloader.
/**
* A parent-last classloader that will try the child classloader first and then the parent.
* This takes a fair bit of doing because java really prefers parent-first.
*
* For those not familiar with class loading trickery, be wary
*/
private static class ParentLastURLClassLoader extends ClassLoader
{
private ChildURLClassLoader childClassLoader;
/**
* This class allows me to call findClass on a classloader
*/
private static class FindClassClassLoader extends ClassLoader
{
public FindClassClassLoader(ClassLoader parent)
{
super(parent);
}
#Override
public Class<?> findClass(String name) throws ClassNotFoundException
{
return super.findClass(name);
}
}
/**
* This class delegates (child then parent) for the findClass method for a URLClassLoader.
* We need this because findClass is protected in URLClassLoader
*/
private static class ChildURLClassLoader extends URLClassLoader
{
private FindClassClassLoader realParent;
public ChildURLClassLoader( URL[] urls, FindClassClassLoader realParent )
{
super(urls, null);
this.realParent = realParent;
}
#Override
public Class<?> findClass(String name) throws ClassNotFoundException
{
try
{
// first try to use the URLClassLoader findClass
return super.findClass(name);
}
catch( ClassNotFoundException e )
{
// if that fails, we ask our real parent classloader to load the class (we give up)
return realParent.loadClass(name);
}
}
}
public ParentLastURLClassLoader(List<URL> classpath)
{
super(Thread.currentThread().getContextClassLoader());
URL[] urls = classpath.toArray(new URL[classpath.size()]);
childClassLoader = new ChildURLClassLoader( urls, new FindClassClassLoader(this.getParent()) );
}
#Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
try
{
// first we try to find a class inside the child classloader
return childClassLoader.findClass(name);
}
catch( ClassNotFoundException e )
{
// didn't find it, try the parent
return super.loadClass(name, resolve);
}
}
}
EDIT: Sergio and ɹoƃı have pointed out that if you call .loadClass with the same classname, you will get a LinkageError. While this is true, the normal use-case for this classloader is to set it as the thread's classloader Thread.currentThread().setContextClassLoader() or via Class.forName(), and that works as-is.
However, if .loadClass() was needed directly, this code could be added in the ChildURLClassLoader findClass method at the top.
Class<?> loaded = super.findLoadedClass(name);
if( loaded != null )
return loaded;
The following code is what I use. It has the advantage over the other answer that it doesn't break the parent chain (you can follow getClassLoader().getParent()).
It also has an advantage over tomcat's WebappClassLoader by not reinventing the wheel and not depending on other objects. It re-uses code from URLClassLoader as much as possible.
(it doesn't yet honor the system class loader, but when I get that fixed I'll update the answer)
It honors the system class loader (for java.* classes, endorsed dir, etc.). It also works when security is turned on and the classloader doesn't have access to its parent (yes, this situation is weird, but possible).
public class ChildFirstURLClassLoader extends URLClassLoader {
private ClassLoader system;
public ChildFirstURLClassLoader(URL[] classpath, ClassLoader parent) {
super(classpath, parent);
system = getSystemClassLoader();
}
#Override
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
if (system != null) {
try {
// checking system: jvm classes, endorsed, cmd classpath, etc.
c = system.loadClass(name);
}
catch (ClassNotFoundException ignored) {
}
}
if (c == null) {
try {
// checking local
c = findClass(name);
} catch (ClassNotFoundException e) {
// checking parent
// This call to loadClass may eventually call findClass again, in case the parent doesn't find anything.
c = super.loadClass(name, resolve);
}
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
#Override
public URL getResource(String name) {
URL url = null;
if (system != null) {
url = system.getResource(name);
}
if (url == null) {
url = findResource(name);
if (url == null) {
// This call to getResource may eventually call findResource again, in case the parent doesn't find anything.
url = super.getResource(name);
}
}
return url;
}
#Override
public Enumeration<URL> getResources(String name) throws IOException {
/**
* Similar to super, but local resources are enumerated before parent resources
*/
Enumeration<URL> systemUrls = null;
if (system != null) {
systemUrls = system.getResources(name);
}
Enumeration<URL> localUrls = findResources(name);
Enumeration<URL> parentUrls = null;
if (getParent() != null) {
parentUrls = getParent().getResources(name);
}
final List<URL> urls = new ArrayList<URL>();
if (systemUrls != null) {
while(systemUrls.hasMoreElements()) {
urls.add(systemUrls.nextElement());
}
}
if (localUrls != null) {
while (localUrls.hasMoreElements()) {
urls.add(localUrls.nextElement());
}
}
if (parentUrls != null) {
while (parentUrls.hasMoreElements()) {
urls.add(parentUrls.nextElement());
}
}
return new Enumeration<URL>() {
Iterator<URL> iter = urls.iterator();
public boolean hasMoreElements() {
return iter.hasNext();
}
public URL nextElement() {
return iter.next();
}
};
}
#Override
public InputStream getResourceAsStream(String name) {
URL url = getResource(name);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
}
return null;
}
}
By reading the source code of either Jetty or Tomcat, both of which provide parent-last class loaders to implement webapp semantics.
https://github.com/apache/tomcat/blob/7.0.93/java/org/apache/catalina/loader/WebappClassLoaderBase.java
Which is to say, by overriding the findClass method in your ClassLoader class. But why reinvent the wheel when you can steal it?
Reading your various updates, I see that you ran into some classic problems with the XML SPI system.
The general problem is this: if you create a completely isolated class loader, then it's hard to use the objects it returns. If you allow sharing, you can have problems when the parent contains the wrong versions of things.
It is to deal with all this lunacy that OSGi was invented, but that's a big pill to swallow.
Even in webapps, the class loaders exempt some packages from the 'local-first' processing on the assumption that the container and the webapp have to agree on the API between them.
(see at the bottom for an update on a solution I found)
It seems that AntClassLoader has a support for parent first/last, (didn't test it yet)
http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/AntClassLoader.java
Here is a snippet
/**
* Creates a classloader for the given project using the classpath given.
*
* #param parent The parent classloader to which unsatisfied loading
* attempts are delegated. May be <code>null</code>,
* in which case the classloader which loaded this
* class is used as the parent.
* #param project The project to which this classloader is to belong.
* Must not be <code>null</code>.
* #param classpath the classpath to use to load the classes.
* May be <code>null</code>, in which case no path
* elements are set up to start with.
* #param parentFirst If <code>true</code>, indicates that the parent
* classloader should be consulted before trying to
* load the a class through this loader.
*/
public AntClassLoader(
ClassLoader parent, Project project, Path classpath, boolean parentFirst) {
this(project, classpath);
if (parent != null) {
setParent(parent);
}
setParentFirst(parentFirst);
addJavaLibraries();
}
Update:
Found this as well, when as a last resort I started guessing class names in google (this is what ChildFirstURLClassLoader produced) - but it seems to be incorrect
Update 2:
The 1st Option (AntClassLoader) is very coupled to Ant (requires a Project context and not easy to pass a URL[] to it
The 2nd Option (from an OSGI project in google code) wasn't quite what I needed as it searched parent classloader before the system classloader (Ant class loader does it correctly by the way). The problem as I see it, think that your parent classloader includes a jar (that it shouldn't have) of a functionality that wasn't on JDK 1.4 but was added in 1.5, this has no harm as the parent last class loader (regular delegation model, e.g. URLClassLoader) will always load first the JDK's classes, but here the child first naive implementation seems to unveil the old, redundant jar in the parent class loader, shadowing the JDK / JRE own implementation.
I have yet to find a certified, fully tested, mature Parent Last / Child First correct implementation that is not coupled to a specific solution (Ant, Catalina/Tomcat)
Update 3 - I found it!
I WAS looking in the wrong place,
All I did was add META-INF/services/javax.xml.transform.TransformerFactory and restored the JDK's com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl instead of the old Xalan's org.apache.xalan.processor.TransformerFactoryImpl
The only reason I don't "accept my own answer" yet, is that I don't know if the META-INF/services approach has the same classloader delegation as regular classes (e.g. is it parent-first / child-last or parent-last / child-first?)
URLClassLoader had this constructor public URLClassLoader(URL[], ClassLoader) that allows you to override the parent classloader of an URLClassLoader. You can just load your classloader through an URLClassLoader with an overridden parent classloader.
You can override findClass() and loadClass() to implement a child first class loader:
/**
* Always throws {#link ClassNotFoundException}. Is called if parent class loader
* did not find class.
*/
#Override
protected final Class findClass(String name)
throws ClassNotFoundException
{
throw new ClassNotFoundException();
}
#Override
protected Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)){
/*
* Check if we have already loaded this class.
*/
Class c = findLoadedClass(name);
if (c == null){
try {
/*
* We haven't previously loaded this class, try load it now
* from SUPER.findClass()
*/
c = super.findClass(name);
}catch (ClassNotFoundException ignore){
/*
* Child did not find class, try parent.
*/
return super.loadClass(name, resolve);
}
}
if (resolve){
resolveClass(c);
}
return c;
}
}

Categories