0

I want to create dynamically a classloader for executing JSR223 script in a controlled environment but failing,

I'm trying remove/add jars using current(parent) ClassLoader, I tried solution Dynamically removing jars from classpath

public class DistributionClassLoader extends ClassLoader {
    public DistributionClassLoader(ClassLoader parent) {
        super(parent);
    }
    private Map<String, ClassLoader> classLoadersByDistribution =
            Collections.synchronizedMap(new WeakHashMap<>());
    private final AtomicReference<String> distribution = new AtomicReference<>();
    @Override
    protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
        final ClassLoader delegate = classLoadersByDistribution.get(distribution.get());
        if (delegate != null) return Class.forName(name, true, delegate);
        throw new ClassNotFoundException(name);
    }
    public void addDistribution(String key, ClassLoader distributionClassLoader){
        classLoadersByDistribution.put(key,distributionClassLoader);
    }
    public void makeDistributionActive(String key){distribution.set(key);}
    public void removeDistribution(String key){
        final ClassLoader toRemove = classLoadersByDistribution.remove(key);
    }
}

But it didn't include all my jars, in test this work

ClassLoader cl = this.getClass().getClassLoader();
Class cls = cl.loadClass("org.springframework.http.HttpStatus");

But using the solution doesn't find class

ClassLoader cl = new DistributionClassLoader(this.getClass().getClassLoader());
Class cls = cl.loadClass("org.springframework.http.HttpStatus");

Exception:

java.lang.ClassNotFoundException: org.springframework.http.HttpStatus
    at com.DistributionClassLoader.loadClass(DistributionClassLoader.java:24)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

How can I select specific jars to add or remove from ClassLoader?

EDIT

I'm able to load jars using @czdepski answer but I still want to remove all/most classes except JDK's

Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});
Ori Marko
  • 56,308
  • 23
  • 131
  • 233
  • Can you explain why exactly you want to remove stuff from class loader? as it's not exactly possible, you can't force-unload class. Classes might be garbage collected with a class loader. Please provide some examples where you need something like that. – GotoFinal Sep 06 '19 at 15:01
  • @gotofinal see https://bz.apache.org/bugzilla/show_bug.cgi?id=63538 – Ori Marko Sep 06 '19 at 15:04
  • I don't see how is this related to removing jars from class loader, when executing a groovy script just need to load it using different class loader that does not use that library. So such libraries should not be part of parent class loader. As removing them from parent class loader will not change anything anyway. – GotoFinal Sep 06 '19 at 15:09
  • @gotofinal how do you create classloader on basis of existing classloader? – Ori Marko Sep 06 '19 at 15:11
  • check if what I wrote in the answer does anything similar to what you need. – GotoFinal Sep 06 '19 at 15:40

1 Answers1

2

You got the delegation wrong. You never check the parent class loader if it has this class.

If we look at the Javadoc for ClassLoader.loadClass(String,boolean) we find:

Loads the class with the specified binary name. The default implementation of this method searches for classes in the following order:

  1. Invoke findLoadedClass(String) to check if the class has already been loaded.

  2. Invoke the loadClass method on the parent class loader. If the parent is null the class loader built into the virtual machine is used, instead.

  3. Invoke the findClass(String) method to find the class.

If the class was found using the above steps, and the resolve flag is true, this method will then invoke the resolveClass(Class) method on the resulting Class object.

Subclasses of ClassLoader are encouraged to override findClass(String), rather than this method.

You did override loadClass, but don't do any delegation to it's parent ClassLoader.
Instead you call classLoadersByDistribution.get(distribution.get());, which is most likely null (hard to tell, but always expect WeakHashMap.get() to return null).

If delegate is not null, then you try to load the class from there. This means the loaded class won't use your ClassLoader to load new classes, but instead the ClassLoader you delegated to.

After all, this sounds like a XY Problem. You want to execute some code using the scripting API and somehow control the environment.
Did you try to use a SecurityManager?


About your comment that you need your own ClassLoader to create a ScriptEngineManager: This ClassLoader is used to search for ScriptEngineFactory implementations. This is done using a service provider interface.
If you don't use your own script engine, this should not matter to you.


If your goal is to add a few jars so the engine can use it, create a new URLClassLoader with the platform class loader as parent. (Or extension class loader, depends on the java version.)
Set this ClassLoader as Thread.setContextClassLoader() and create the ScriptEngine.

If you did choose the parent of the URLClassLoader correctly, it will not see classes loadable by the application class loader.

Community
  • 1
  • 1
Johannes Kuhn
  • 14,778
  • 4
  • 49
  • 73
  • About last commet, I need ClassLoader, see https://docs.oracle.com/javase/7/docs/api/javax/script/ScriptEngineManager.html#ScriptEngineManager(java.lang.ClassLoader) – Ori Marko Sep 05 '19 at 14:36
  • So you want to provide your own ScriptEngine? Loaded through your own ClassLoader? – Johannes Kuhn Sep 05 '19 at 15:07
  • I want to update(add/remove jars) using current classloader before sending to ScriptEngine – Ori Marko Sep 05 '19 at 15:15
  • You should not modify a ClassLoader after it's creation. It's dangerous. (See the JVMS about loading classes.) – Johannes Kuhn Sep 05 '19 at 15:28
  • but I want to use a more secure/configrable classloader on a basis of existing classloader – Ori Marko Sep 05 '19 at 15:31
  • More secure than `URLClassLoader`? Good luck with that. No really, what do you want? – Johannes Kuhn Sep 05 '19 at 15:37
  • Yeah, see my last paragraph. Don't implement your own ClassLoader. It's tricky to do, so just use an `URLClassLoader` as context class loader when creating the engine. – Johannes Kuhn Sep 05 '19 at 15:56
  • But then I failed to add jars, see https://stackoverflow.com/questions/56864832/create-classloader-with-only-classes-inside-specific-folder?r=SearchResults – Ori Marko Sep 05 '19 at 15:58
  • Use the right parent when creating the URLClassLoader. On java 7 that might mean using `null` as parent. Which might exclude classes loaded via extension class loader. – Johannes Kuhn Sep 05 '19 at 16:00