【问题标题】:Why can't System.setProperty() change the classpath at runtime?为什么 System.setProperty() 不能在运行时更改类路径?
【发布时间】:2008-11-07 08:21:01
【问题描述】:

我指的是question以编程方式更改类路径。

我阅读并发现 System 类下有一些函数作为 getproperties,我们可以在其中检索属性,然后也可以使用 setProperties() 进行设置。

但我得到的答案是它不起作用。我自己没有尝试过,但是,我正在接听电话。

只是澄清一下,如果这些 setProperty() 和 getProperty() 方法在运行时无法更改,为什么会存在这些方法。还是仅特定于类路径属性?

如果有人能提出一个他们真正有帮助的场景,我将不胜感激?

【问题讨论】:

  • 添加了关于类路径属性的信息
  • 注意:Beanshell(以及 Ant 和 Groovy)能够动态加载 .jars,但问题是对于某些事情,尝试在 beanshells 自定义类加载器中加载它们是不受欢迎的。所以,它并不总是有效,但大部分时间都有效。

标签: java classpath


【解决方案1】:

您当然可以在任何时候设置您想要的任何系统属性。问题是,会有影响吗?对于类路径,答案是否定的。系统类加载器在启动序列的早期初始化。它将类路径复制到自己的数据结构中,并且不再读取类路径属性。更改它不会影响系统中的任何内容。

造成这种情况的原因可能有两个。次要原因是性能。您可能需要构建某种数据结构以快速查找资源,并且每次重新解析类路径可能效率低下。更重要的原因是安全性。您不希望流氓类更改您下面的类路径并加载另一个类的受损版本。

【讨论】:

    【解决方案2】:

    修改类路径

    即使你不能使用系统属性设置类路径(因为JVM读取系统属性一次:在启动时),你仍然可以通过强制调用类加载器的addURL方法来改变类路径。请注意,以下解决方案未考虑当前线程。因此,它可能并非在所有情况下都准确。

    示例解决方案

    以下代码在 Sun 网站上的原始来源已被删除:

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;                   
    
    import java.io.File;
    import java.io.IOException;
    
    import java.net.URL;
    import java.net.URLClassLoader;
    
    /**
     * Allows programs to modify the classpath during runtime.              
     */                                                                     
    public class ClassPathUpdater {                                         
      /** Used to find the method signature. */                             
      private static final Class[] PARAMETERS = new Class[]{ URL.class };   
    
      /** Class containing the private addURL method. */
      private static final Class<?> CLASS_LOADER = URLClassLoader.class;
    
      /**
       * Adds a new path to the classloader. If the given string points to a file,
       * then that file's parent file (i.e., directory) is used as the
       * directory to add to the classpath. If the given string represents a
       * directory, then the directory is directly added to the classpath.
       *
       * @param s The directory to add to the classpath (or a file, which
       * will relegate to its directory).
       */
      public static void add( String s )
        throws IOException, NoSuchMethodException, IllegalAccessException,
               InvocationTargetException {
        add( new File( s ) );
      }
    
      /**
       * Adds a new path to the classloader. If the given file object is
       * a file, then its parent file (i.e., directory) is used as the directory
       * to add to the classpath. If the given string represents a directory,
       * then the directory it represents is added.
       *
       * @param f The directory (or enclosing directory if a file) to add to the
       * classpath.
       */
      public static void add( File f )
        throws IOException, NoSuchMethodException, IllegalAccessException,
               InvocationTargetException {
        f = f.isDirectory() ? f : f.getParentFile();
        add( f.toURI().toURL() );
      }
    
      /**
       * Adds a new path to the classloader. The class must point to a directory,
       * not a file.
       *
       * @param url The path to include when searching the classpath.
       */
      public static void add( URL url )
        throws IOException, NoSuchMethodException, IllegalAccessException,
               InvocationTargetException {
        Method method = CLASS_LOADER.getDeclaredMethod( "addURL", PARAMETERS );
        method.setAccessible( true );
        method.invoke( getClassLoader(), new Object[]{ url } );
      }
    
      private static URLClassLoader getClassLoader() {
        return (URLClassLoader)ClassLoader.getSystemClassLoader();
      }
    }
    

    该链接不再有效:http://forums.sun.com/thread.jspa?threadID=300557

    示例用法

    以下示例将在运行时将/home/user/dev/java/app/build/com/package 添加到类路径:

    try {
      ClassPathUpdater.add( "/home/user/dev/java/app/build/com/package/Filename.class" );
    }
    catch( Exception e ) {
      e.printStackTrace();
    }
    

    【讨论】:

    • 您的第一个链接已关闭....顺便说一句,这似乎假设应用程序的类加载器是 URLClassLoader 类型,可能不是这样的,不是吗?
    • @Pacerier:谢谢。可以有一个自定义 URLClassLoader。通过查找ClassLoader (docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html) 的所有子类并尝试使用二进制类名调用它们的loadClass 方法,可以使解决方案更加健壮。
    • 我的意思是默认的应用程序类加载器是由 system 提供的,它可能不是 URLClassLoader。 (可能完全是另外一回事)。
    • 你是对的。 JVM 实现有两个加载器:一个引导加载器和一个用户类加载器。 ClassLoaders 不加载数组类。 docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3 然而,对于大多数实际用途,JVM 带有一个 URLClassLoader,可用于在运行时注入新的类路径。
    【解决方案3】:

    System.setProperty 可用于在程序开始时设置一些安全或协议处理程序。喜欢:

    /*
    Add the URL handler to the handler property. This informs 
    IBMJSSE what URL handler to use to handle the safkeyring 
    support. In this case IBMJCE.
    */
    System.setProperty("java.protocol.handler.pkgs", "com.ibm.crypto.provider");
    

    using SSL

    System.setProperty("javax.net.ssl.keyStore", context.getRealPath(KEYSTORE));
    System.setProperty("javax.net.ssl.keyStorePassword", "password");
    System.setProperty("javax.net.ssl.trustStore", context.getRealPath(TRUSTSTORE));
    System.setProperty("javax.net.debug", "ssl");
    HttpClient httpClient = new HttpClient();
    GetMethod httpGet = new GetMethod("https://something.com");
    httpClient.executeMethod(httpGet);
    return new String(httpGet.getResponseBody());
    

    但要小心,因为它会在运行时更改所有在同一 jvm 中运行的应用程序的环境
    例如,如果一个应用程序需要用 saxon 运行,而另一个应用程序需要用 xalan 运行,并且都使用 System.setProperty 来设置transformerFactory,那么您将遇到麻烦

    正如Monitored System.setProperty文章中所说,
    System.setProperty() 可能是一个邪恶的调用。

    • 它是 100% 线程敌对的
    • 它包含超全局变量
    • 当这些变量在运行时发生神秘变化时,调试起来非常困难

    关于classpath属性,如I said in a previous question,作为runtime不能轻易更改。

    特别是java系统属性java.class.path用于在JRE实例化时建立链接链接,然后不再重读。因此,您对属性所做的更改实际上不会对现有虚拟机产生任何影响。

    【讨论】:

    • 感谢 VonC,确实提供了丰富的信息。但是,为什么类路径属性的行为不同。当你在程序开始时说,我假设程序已经执行,这是它正在做的第一件事。正确吗?
    • 是的,java.class.path 系统属性更多的是对开发人员的信息礼貌。与所有属性一样,您可以通过 setProperty() 更改它,但这样做只不过是更改地图中的字符串。
    • 我认为 java.security.properties 是另一个系统属性,它在 JVM 启动时被读取并且不再被引用。我想在运行时更改任一属性的能力会导致比它解决的问题更多。
    • “超级”全局变量和普通全局变量之间有区别吗? :-)
    • @JesperE:是的,它来自已知全局变量的范围。一个超级全局变量在整个程序中都是已知的,不需要任何导入或前缀(如 PHP 中的 $_GET)。在这里,所有在 JVM 中运行的应用程序都知道一个超级变量,而不仅仅是像普通全局变量那样来自一个应用程序。
    【解决方案4】:

    还有一种方法可以在运行时更改 java.library.path,要做到这一点,只需这样做:

    System.setProperty( "java.library.path", newPath);
    Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
    fieldSysPath.setAccessible(true);
    fieldSysPath.set(null, null); // that's the key.
    

    当 ClassLoader 类中的这个私有静态字段设置为 null 时,下次尝试加载本机库时 ClassLoader 将使用 java.library.path 中的新值再次初始化。

    【讨论】:

      【解决方案5】:

      getProperty() 的基本思想是可以从 JVM 外部配置程序/代码,使用 java -Dfoo=bar 语法在命令行上传递属性。

      在您无法控制命令行的情况下,您可能希望在其他软件组件(例如日志记录组件)中配置某些行为 - 考虑部署在 Servlet 容器中 - setProperty() 以一种以编程方式更改设置的便捷方法,例如,在实例化您的日志记录实用程序之前。

      classpath 问题所展示的问题是,程序通常只会在首次初始化此类系统属性时仅读取一次。因此,在 JVM 启动后更改类路径不会对您的应用程序本身产生任何影响,因为 JVM 已经初始化,并且在您已经获得 Logger 实例(或其他)之后更改一些日志记录配置,通常也不会产生任何影响.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-04-15
        • 1970-01-01
        • 2013-02-13
        • 2019-04-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-29
        相关资源
        最近更新 更多