在Java中,是否有可能动态地“添加”类路径?

23
java -classpath ../classes;../jar;. parserTester

我如何以编程方式获取上述命令中的功能?比如,是否可以运行如下:

java parserTester

如何获得相同的结果?我尝试使用URLClassLoader,但它修改了类路径并且没有添加到其中。

谢谢!


感谢您的回复Milhous。 但这正是我想做的...如何先将jar文件加入类路径中? 我也尝试使用自定义类加载器 :(

那很有用..但是抱歉我只需要运行它:java parserTester。 我想知道是否可能实现这样的事情???

这需要这样做是因为我将parserTester.java和.class放在一个单独的文件夹中。我需要保留文件结构。 parserTester使用单独的jar文件夹中的一个jar文件。


你必须在类路径中以1个类/ JAR开始。 - Milhous
为什么需要使用Java解析器测试? - Milhous
1
你为什么想在运行时动态调整类路径? - Stephane Grenier
注意:要加载您的未打包的解析器测试器,您可能仍需要使用"-classpath .",可以明确地指定或在环境变量中隐式使用。 - Lawrence Dol
重复。https://dev59.com/BHVC5IYBdhLWcg3whBaj#1198693 - Dave Jarvis
我认为我可能已经找到了答案 http://stackoverflow.com/a/14664534/849697。在你的classpath环境变量中,你需要在你的jar文件夹后面加上反斜杠星号,这样它就会查看文件夹中的所有jar文件。如果你遇到有关库的错误,请将库文件夹添加到你的路径环境变量中。 - xxjjnn
7个回答

22
您可以使用 java.net.URLClassLoader 来加载具有程序自定义 URL 列表的类:

public class URLClassLoader extends SecureClassLoader

此类加载器用于从指向 JAR 文件和目录的 URL 的搜索路径中加载类和资源。 任何以“/”结尾的 URL 都被认为是指向目录的。 否则,假定该 URL 指向将根据需要打开的 JAR 文件。

创建 URLClassLoader 实例的线程的 AccessControlContext 将在随后加载类和资源时使用。

加载的类默认只被授予访问创建 URLClassLoader 时指定的 URL 的权限。

Since:1.2

稍微改进下可以使用通配符路径名来选择整个目录中的JAR。(此代码包含一些有用的工具方法的引用,但在上下文中它们的实现应该是明显的):
/**
 * Add classPath to this loader's classpath.
 * <p>
 * The classpath may contain elements that include a generic file base name.  A generic basename
 * is a filename without the extension that may begin and/or end with an asterisk.  Use of the
 * asterisk denotes a partial match. Any files with an extension of ".jar" whose base name match
 * the specified basename will be added to this class loaders classpath.  The case of the filename is ignored.
 * For example "/somedir/*abc" means all files in somedir that end with "abc.jar", "/somedir/abc*"
 * means all files that start with "abc" and end with ".jar", and "/somedir/*abc*" means all files
 * that contain "abc" and end with ".jar".
 *
 */
public void addClassPath(String cp) {
    String                              seps=File.pathSeparator;                // separators

    if(!File.pathSeparator.equals(";")) { seps+=";"; }                          // want to accept both system separator and ';'
    for(StringTokenizer st=new StringTokenizer(cp,seps,false); st.hasMoreTokens(); ) {
        String pe=st.nextToken();
        File   fe;
        String bn=null;

        if(pe.length()==0) { continue; }

        fe=new File(pe);
        if(fe.getName().indexOf('*')!=-1) {
            bn=fe.getName();
            fe=fe.getParentFile();
            }

        if(!fe.isAbsolute() && pe.charAt(0)!='/' && pe.charAt(0)!='\\') { fe=new File(rootPath,fe.getPath()); }
        try { fe=fe.getCanonicalFile(); }
        catch(IOException thr) {
            log.diagln("Skipping non-existent classpath element '"+fe+"' ("+thr+").");
            continue;
            }
        if(!GenUtil.isBlank(bn)) {
            fe=new File(fe,bn);
            }
        if(classPathElements.contains(fe.getPath())) {
            log.diagln("Skipping duplicate classpath element '"+fe+"'.");
            continue;
            }
        else {
            classPathElements.add(fe.getPath());
            }

        if(!GenUtil.isBlank(bn)) {
            addJars(fe.getParentFile(),bn);
            }
        else if(!fe.exists()) {                                                 // s/never be due getCanonicalFile() above
            log.diagln("Could not find classpath element '"+fe+"'");
            }
        else if(fe.isDirectory()) {
            addURL(createUrl(fe));
            }
        else if(fe.getName().toLowerCase().endsWith(".zip") || fe.getName().toLowerCase().endsWith(".jar")) {
            addURL(createUrl(fe));
            }
        else {
            log.diagln("ClassPath element '"+fe+"' is not an existing directory and is not a file ending with '.zip' or '.jar'");
            }
        }
    log.diagln("Class loader is using classpath: \""+classPath+"\".");
    }

/**
 * Adds a set of JAR files using a generic base name to this loader's classpath.  See @link:addClassPath(String) for
 * details of the generic base name.
 */
public void addJars(File dir, String nam) {
    String[]                            jars;                                   // matching jar files

    if(nam.endsWith(".jar")) { nam=nam.substring(0,(nam.length()-4)); }

    if(!dir.exists()) {
        log.diagln("Could not find directory for Class Path element '"+dir+File.separator+nam+".jar'");
        return;
        }
    if(!dir.canRead()) {
        log.error("Could not read directory for Class Path element '"+dir+File.separator+nam+".jar'");
        return;
        }

    FileSelector fs=new FileSelector(true).add("BaseName","EG",nam,true).add("Name","EW",".jar",true);
    if((jars=dir.list(fs))==null) {
        log.error("Error accessing directory for Class Path element '"+dir+File.separator+nam+".jar'");
        }
    else if(jars.length==0) {
        log.diagln("No JAR files match specification '"+new File(dir,nam)+".jar'");
        }
    else {
        log.diagln("Adding files matching specification '"+dir+File.separator+nam+".jar'");
        Arrays.sort(jars,String.CASE_INSENSITIVE_ORDER);
        for(int xa=0; xa<jars.length; xa++) { addURL(createUrl(new File(dir,jars[xa]))); }
        }
    }

private URL createUrl(File fe) {
    try {
        URL url=fe.toURI().toURL();
        log.diagln("Added URL: '"+url.toString()+"'");
        if(classPath.length()>0) { classPath+=File.pathSeparator; }
        this.classPath+=fe.getPath();
        return url;
        }
    catch(MalformedURLException thr) {
        log.diagln("Classpath element '"+fe+"' could not be used to create a valid file system URL");
        return null;
        }
    }

软件猴。我不理解这个问题。它看起来很高级,但他给出的解释完全没有意义:“因为我有parserTester.java和.class在一个单独的文件夹中。我需要保留文件结构。”我认为你的答案适用于更正确的问题 :) +1 - OscarRyz

1
我必须同意其他两位发帖者的观点,听起来你正在过度复杂化一个测试类。 在不通过编程更改类路径的情况下,将.java和.class文件放在不同的文件夹中,同时依赖于第三个jar文件并不那么不寻常。 如果您这样做是因为您不想每次在命令行上键入类路径,我建议使用shell脚本或批处理文件。更好的选择是使用IDE。 我真正想知道的问题是,为什么您要尝试在代码中管理类路径?

0

我认为您想要的是"Execution Wrapper"或特定平台的"Launcher"...通常,该组件用于检测您的操作系统、架构和依赖项,并在启动应用程序之前进行调整。这是一种老派的设计模式(80年代及以前),但今天仍然被广泛使用。其想法是,你的程序可以是系统和环境无关的,而启动器将做好准备工作并告诉软件它所需的一切。许多现代开源程序都使用Shell脚本和批处理文件等方式实现,例如Apache Tomcat。您也可以很容易地使用Java编写包装器,并使用命令行执行它来启动软件(在*NIX中,确保在exec命令的末尾添加"&",这样您的包装器就可以退出了,只留下您的软件运行...也可以让您关闭shell窗口而不会杀死进程)。


0
你可以实现自己的类加载器,但是该类/ jar文件必须在类路径中才能执行。

尝试一下

java -cp *.jar:. myClass

或者

export CLASSPATH=./lib/tool.jar:.
java myClass

或者

java -jar file.jar

0
您可以编写批处理文件或shell脚本文件来导出类路径并运行Java程序。 在Windows中,
set classpath=%classpath%;../classes;../jars/* java ParserTester

在Unix中, export classpath=%classpath%:../classes:../jars/* java ParserTester 如果您将文件命名为parser.bat或parser.sh,则可以通过在各自的操作系统中调用parser来运行它。
从Java 1.6开始,您可以通过说/*将目录中的所有jar包包含到类路径中。
如果您正在尝试动态生成Java文件、编译并添加到类路径中,请预先将生成的类文件所在的目录设置为类路径。它应该会加载类。 如果您正在修改已经生成的Java类,基本上是在修改后重新编译,并且如果您想要加载新类,则需要使用自定义类加载器以避免缓存类。

-1

我理解得对吗?!你唯一的原因就是想在运行时启动类而不指定类路径并加载它?...

java parserTester

而不是

java -classpath ../classes;../jar;. parserTester

也许我没有理解你的原因。但如果这是你想要的(尽管我觉得这没有多大意义),你可以按照以下步骤操作:

  • 启动类
  • 从主方法中启动另一个类,并在程序中设置类路径。
  • 历史结束。

类似以下的“java-伪代码”:

public static void main( String [] args ) {
    String classpath = "classes;../jar";
    Runtime.getRuntime().execute("java + classpath + " parserTester ");
}

请告诉我是否理解正确。如果您想做其他事情,我很乐意提供帮助。


-2

非常好的帖子,在我的情况下,我这样做可以很好地工作(注意:仅适用于Windows):

set classpath=%classpath%;../lib/*
java -cp %classpath% com.test.MyClass 

这种方法回答了一个与帖子有些不同的问题。使用Java 1.6+中可用的“通配符”类路径方法,可以加载给定文件夹中的所有JAR /类,这将使您无需明确定义每个文件,但对于动态加载没有帮助。 - jwj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接