Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove folder from Java classpath at runtime

Tags:

java

Is there a way to remove a folder from the classpath similar to adding a folder at runtime (Can a directory be added to the class path at runtime?)

like image 788
jpints14 Avatar asked Jan 07 '16 17:01

jpints14


3 Answers

Please find below a snippet as technical example to demonstrate adding / removing a path.

create following source files in any directory

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Stack;
import sun.misc.URLClassPath;

public class EvilPathDemo {

    public static void addPath(String path) throws Exception {
        URL u = new File(path).toURI().toURL();
        URLClassLoader urlClassLoader = (URLClassLoader)
            ClassLoader.getSystemClassLoader();
        Class<?> urlClass = URLClassLoader.class;
        Method method = urlClass.getDeclaredMethod("addURL",
                new Class[]{URL.class}
        );
        method.setAccessible(true);
        method.invoke(urlClassLoader, new Object[]{u});
    }

    public static void removePath(String path) throws Exception {
        URL url = new File(path).toURI().toURL();
        URLClassLoader urlClassLoader = (URLClassLoader) 
            ClassLoader.getSystemClassLoader();
        Class<?> urlClass = URLClassLoader.class;
        Field ucpField = urlClass.getDeclaredField("ucp");
        ucpField.setAccessible(true);
        URLClassPath ucp = (URLClassPath) ucpField.get(urlClassLoader);
        Class<?> ucpClass = URLClassPath.class;
        Field urlsField = ucpClass.getDeclaredField("urls");
        urlsField.setAccessible(true);
        Stack urls = (Stack) urlsField.get(ucp);
        urls.remove(url);
    }

    public static void main(String[] args) throws Exception {
        String parm = args.length == 1 ? args[0] : "";
        String evilPath = "/tmp";

        String classpath = System.getProperty("java.class.path");
        boolean isEvilPathSet = false;
        for (String path : classpath.split(File.pathSeparator)) {
            if (path.equalsIgnoreCase(evilPath)) {
                System.out.printf("evil path '%s' in classpath%n", evilPath);
                isEvilPathSet = true;
                break;
            }
        }
        if (isEvilPathSet && parm.equalsIgnoreCase("REMOVE")) {
            System.out.printf("evil path '%s' will be removed%n", evilPath);
            removePath(evilPath);
        }
        tryToLoad("Foo");
        if (parm.equalsIgnoreCase("ADD")) {
            System.out.printf("evil path '%s' will be added%n", evilPath);
            addPath(evilPath);
        }
        tryToLoad("Bar");
    }

    private static void tryToLoad(String className) {
        try {
            Class<?> foo = Class.forName(className);
            System.out.printf("class loaded: %s%n", foo.getName());
        } catch (ClassNotFoundException ex) {
            System.out.println(ex);
        }
    }
}

.

public class Foo {
    static {
        System.out.println("I'm foo...");
    }
}

.

public class Bar {
    static {
        System.out.println("I'm bar...");
    }
}

compile them as follow

javac EvilPathDemo.java
javac -d /tmp Foo.java Bar.java

During the test we will try to load the classes Foo and Bar.

without /tmp in the classpath

java -cp . EvilPathDemo
java.lang.ClassNotFoundException: Foo
java.lang.ClassNotFoundException: Bar

adding /tmp to the classpath

java -cp . EvilPathDemo add
java.lang.ClassNotFoundException: Foo
evil path '/tmp' will be added
I'm bar...
class loaded: Bar

with /tmp in the classpath

java -cp .:/tmp EvilPathDemo
evil path '/tmp' in the classpath
I'm foo...
class loaded: Foo
I'm bar...
class loaded: Bar

remove /tmp from the classpath

java -cp .:/tmp EvilPathDemo remove
evil path '/tmp' in the classpath
evil path '/tmp' will be removed
java.lang.ClassNotFoundException: Foo
java.lang.ClassNotFoundException: Bar

During the testing I found out that following cases are not working.

  • addPath(evilPath);
    tryToLoad("Foo");
    removePath(evilPath); // had not effect
    tryToLoad("Bar");
  • removePath(evilPath);
    tryToLoad("Foo");
    addPath(evilPath); // had no effect
    tryToLoad("Bar");
  • tryToLoad("Foo");
    removePath(evilPath); // had no effect
    tryToLoad("Bar");

I did not spent time to find out why. Because I don't see any practical use in it. If you really need/wish to play with the classpaths have a look how classloaders are working.

like image 154
SubOptimal Avatar answered Oct 08 '22 03:10

SubOptimal


The removePath method from above did not work for me and my Weld Container, the url stack was always emtpy. The following ugly smugly method worked:

public static void removeLastClasspathEntry() throws Exception {
    URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
    Class<?> urlClass = URLClassLoader.class;
    Field ucpField = urlClass.getDeclaredField("ucp");
    ucpField.setAccessible(true);
    URLClassPath ucp = (URLClassPath) ucpField.get(urlClassLoader);

    Field loadersField = URLClassPath.class.getDeclaredField("loaders");
    loadersField.setAccessible(true);
    List jarEntries = (List) loadersField.get(ucp);
    jarEntries.remove(jarEntries.size() - 1);

    Field pathField = URLClassPath.class.getDeclaredField("path");
    pathField.setAccessible(true);
    List pathList = (List) pathField.get(ucp);
    URL jarUrl = (URL) pathList.get(pathList.size() - 1);
    String jarName = jarUrl.toString();
    pathList.remove(pathList.size() - 1);

    Field lmapField = URLClassPath.class.getDeclaredField("lmap");
    lmapField.setAccessible(true);
    Map lmapMap = (Map) lmapField.get(ucp);
    lmapMap.remove(jarName.replaceFirst("file:/", "file:///"));
}
like image 33
Kuronashi Avatar answered Oct 08 '22 05:10

Kuronashi


Class loaders can be nested so instead of modifying the system class loader which is the root of the tree of class loaders, it is better to simply create a nested classloader and use that to load classes.

The system classloader itself is immutable (for good reasons) but you can do whatever you want in nested class loaders, including destroying them to unload classes and resources. This is commonly used in e.g. osgi and application servers to load/unload e.g. plugins, applications, etc.

For nested class loaders you can completely customize how to load classes. The URLClassloader is probably a good starting point for what you want.

like image 34
Jilles van Gurp Avatar answered Oct 08 '22 03:10

Jilles van Gurp