简体   繁体   English

在运行时从Java类路径中删除文件夹

[英]Remove folder from Java classpath at runtime

有没有办法从类路径中删除文件夹,类似于在运行时添加文件夹( 可以在运行时将目录添加到类路径吗?

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 . 在测试期间,我们将尝试加载类FooBar

without /tmp in the classpath 在类路径中没有/ tmp

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

adding /tmp to the classpath 将/ tmp添加到类路径中

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 在类路径中使用/ tmp

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 从类路径中删除/ tmp

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); 让addpath(evilPath);
    tryToLoad("Foo"); tryToLoad( “富”);
    removePath(evilPath); removePath(evilPath); // had not effect //没影响
    tryToLoad("Bar"); tryToLoad( “酒吧”);
  • removePath(evilPath); removePath(evilPath);
    tryToLoad("Foo"); tryToLoad( “富”);
    addPath(evilPath); 让addpath(evilPath); // had no effect //无效
    tryToLoad("Bar"); tryToLoad( “酒吧”);
  • tryToLoad("Foo"); tryToLoad( “富”);
    removePath(evilPath); removePath(evilPath); // had no effect //无效
    tryToLoad("Bar"); tryToLoad( “酒吧”);

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. 如果你真的需要/希望使用类路径,看看类加载器是如何工作的。

The removePath method from above did not work for me and my Weld Container, the url stack was always emtpy. 上面的removePath方法对我和我的焊接容器都不起作用,url堆栈总是空的。 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:///"));
}

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 eg osgi and application servers to load/unload eg plugins, applications, etc. 这通常用于例如osgi和应用程序服务器来加载/卸载例如插件,应用程序等。

For nested class loaders you can completely customize how to load classes. 对于嵌套类加载器,您可以完全自定义如何加载类。 The URLClassloader is probably a good starting point for what you want. URLClassloader可能是你想要的一个很好的起点。

I dont think there is a straight forward way to do it. 我不认为有一个直接的方法来做到这一点。 You can follow : 您可以关注:

  • Get class path variables using : System.getenv("CLASSPATH") . 使用以下命令获取类路径变量: System.getenv("CLASSPATH") It will return semi colon separated values. 它将返回半冒号分隔值。

    String classPath = System.getenv("CLASSPATH")

  • Take the folder path as input and replace it with "" like : 将文件夹路径作为输入,并将其替换为“”,如:

    String remainigPath = classPath.replace(inputpath,"");

  • Put the remaining paths in an array using split method. 使用split方法将其余路径放在数组中。

    String[] paths = remainigPath .split(";");

  • For adding classPath, You already have the code. 要添加classPath,您已经拥有了代码。

I had the same issue, so I tackled it by creating a library that works on every ClassLoader that uses a URLClassPath (so, currently, URLClassLoader ). 我有同样的问题,所以我通过创建一个库来处理它,该库适用于使用URLClassPath每个ClassLoader (因此,目前, URLClassLoader )。

The library has methods for: 该库有以下方法:

  • Adding new entries in front 在前面添加新条目
  • Appending new entries 附加新条目
  • Remove existing entries 删除现有条目

Please note that, since this library accesses internal and proprietary APIs, it is no guaranteed to work in future versions of the JDK. 请注意,由于此库访问内部和专有API,因此无法保证在将来的JDK版本中工作。 It currently does for Java 7 and Java 8 (Oracle and OpenJDK). 它目前适用于Java 7和Java 8(Oracle和OpenJDK)。

Here is the GitHub page (contribution is appreciated), and here is the Maven Central artifact 这是GitHub页面 (感谢贡献),这里是Maven Central工件

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM