【问题标题】:Loading classes and resources in Java 9在 Java 9 中加载类和资源
【发布时间】:2017-12-23 07:41:09
【问题描述】:

我正在阅读 this article on InfoQ 引用 Reinhold:

开发人员仍然可以将 Java 9 中的 Java 类路径用于 Java 运行时搜索类和资源文件。就是这样 Java 9 的模块,开发者不再需要类路径。

所以现在我的问题是:执行上述任务的正确 Java 9 方法是什么?你如何动态加载例如一张图片(没有摆弄相对路径)?

更有趣的是,如何检查一个类是否可用并动态做出决定(例如,检查 Jackson 是否可用,如果可用,将其用于 JSON 序列化,如果不使用其他东西)?

文章还提到 Spring Boot 已经支持 Java 9,而且 Spring Boot 肯定做了很多动态加载。那么也许有人知道我可以查看的 Spring 代码价格?

【问题讨论】:

    标签: java spring-boot java-9 java-platform-module-system module-path


    【解决方案1】:

    首先,为了澄清事实,我既没有说也没有写文字 以上引用。我永远不会那样说的。这只是草率 报告所涉及的出版物。

    关于类加载和资源的最重要的理解 Java 9 中的查找是,从根本上说,它们没有改变。 您可以像往常一样搜索课程和资源 有,通过调用Class::forName 和各种getResource* 方法 在ClassClassLoader 类中,无论您的代码是否 从类路径或模块路径加载。还是三个 内置类加载器,就像在 JDK 1.2 中一样,它们有 相同的委托关系。因此,许多现有代码只是 开箱即用。

    有一些细微差别,如 JEP 261 中所述:具体类型 的内置类加载器已经改变,一些类以前 由引导类加载器加载的现在由平台类加载 加载程序以提高安全性。现有代码假定一个 内置类加载器是URLClassLoader,或者一个类是由 引导类加载器,因此可能需要稍作调整。

    最后一个重要的区别是模块中的非类文件资源 默认情况下被封装,因此cannot be located from outside the module unless their effective package is open。 要从您自己的模块加载资源,最好使用 ClassModule 中的资源查找方法,可以找到任何 模块中的资源,而不是 ClassLoader 中的资源,这可以 仅在模块的open 包中查找非类文件资源。

    【讨论】:

      【解决方案2】:

      [编辑:这个答案是在马克的权威答案之前写的。我已经修改了我的以提供一个简单的示例,可用on GitHub。]

      根据this video,Java 9 中的类加载没有改变。

      例如,假设我们有:

      • 一个example.jar,在包net.codetojoy.example.resources中包含一个图像
      • 为了加强罐子,net.codetojoy.example.Composer 是公开的(并在适用的情况下导出)
      • 一个简单的App 类,它使用example.jar 作为库并尝试从中加载图像

      App中的相关代码:

      static InputStream getResourceAsStream(String resource) 
          throws Exception {
      
          // Load net/codetojoy/example/resource/image.jpg
          // Assume net.codetojoy.example.Composer is public/exported
          // resource is 'resource/image.jpg'
      
          InputStream result = Composer.class.getResourceAsStream(resource);
      
          return result;
      }   
      

      以下是 JDK 9 中 example.jar 的几个案例:

      老式的非模块化罐子

      如果example.jar 不是模块,则代码可以正常工作。类加载不变。

      带开放包装的模块化罐

      在这种情况下,这是module-info.java 文件:

      module net.codetojoy.example {
          // export the Composer class
          exports net.codetojoy.example;
      
          // image is available
          opens net.codetojoy.example.resources;
      }
      

      在这种情况下,图像可以被客户端加载,因为包是打开的。

      没有开包的模块化罐子

      在这种情况下,module-info.java 是:

      module net.codetojoy.example {
          // export the Composer class
          exports net.codetojoy.example;
      
          // package not opened: image not available
          // opens net.codetojoy.example.resources;
      }
      

      在这种情况下,由于强封装,无法加载图像:模块通过不打开包来保护图像。

      完整来源here on GitHub

      【讨论】:

      • 谢谢,但是那些使用不支持module-info.java 的旧语言级别的人呢?但是我们仍然遇到资源加载器的问题?对于希望在不采用新的 JDK9 语言级别的情况下很好地使用 JRE9 的开发人员是否有任何让步?
      • 没关系,我的问题是使用Foo.class.getClass().getResourceAsStream(...)Foo.class.getResourceAsStream(...)。 JDK9 对前者的处理似乎与 JDK8、JDK7 不同。
      【解决方案3】:

      除了现有的答案,我想举例说明不同资源目录名称的非类文件资源的封装规则。

      getResourceAsStream 的规范表明,如果 包名 是从其名称派生的,则资源被封装。

      所以如果resource’s directory name is NOT a valid Java identifier,它没有被封装。这意味着如果一个模块有一个资源位于例如一个名为 dir-1 的目录下(在其名称中包含一个无效字符 -),它将始终可以从模块外部访问。

      这是两个 Java 模块 (source code in GitHub) 的示例。 模块 1 包含以下资源文件:

      ├── dir-3
      │   └── resource3.txt
      ├── dir1
      │   └── resource1.txt
      ├── dir2
      │   └── resource2.txt
      └── root.txt
      

      module-info.java:

      module module_one {
        opens dir1;
      }
      

      模块 2 需要 module-info.java 中的 模块 1

      module module_two {
        requires module_one;
      }
      

      并有一个用于加载各种资源文件的示例主类:

      package module2;
      
      import java.io.IOException;
      
      public class Main {
        public static void main(String[] args) throws IOException {
          loadResource("root.txt", "From module's root directory");
          loadResource("dir1/resource1.txt", "From opened package `dir1`");
          loadResource("dir2/resource2.txt", "From internal package `dir2`");
          loadResource("dir-3/resource3.txt", "From directory `dir-3` with non-Java name");
        }
      
        public static void loadResource(String name, String comment) throws IOException {
          // module2 application class loader
          final var classLoader = Main.class.getClassLoader();
          try (var in = classLoader.getResourceAsStream(name)) {
            System.out.println();
            System.out.println("// " + comment);
            System.out.println(name + ": " + (in != null));
          }
        }
      }
      

      运行上面的类会得到以下输出:

      // From module's root directory
      root.txt: true
      
      // From opened package `dir1`
      dir1/resource1.txt: true
      
      // From internal package `dir2`
      dir2/resource2.txt: false
      
      // From directory `dir-3` with non-Java name
      dir-3/resource3.txt: true
      

      你可以看到根目录和dir-3目录的资源文件没有被封装,因此模块2可以加载它们。

      包裹dir1被封装但无条件打开。 模块 2 也可以加载它。

      包裹dir2被封装,没有打开。 模块 2 无法加载。

      请注意,Module 2 不能在 dir1dir2 目录下包含自己的资源,因为它们已经封装在 Module 1 中。如果您尝试添加dir1,您将收到以下错误:

      Error occurred during initialization of boot layer
      java.lang.LayerInstantiationException: Package dir1 in both module module_one and module module_two
      

      这是一个Flyway related issue 供参考。

      【讨论】:

        猜你喜欢
        • 2018-07-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-26
        • 2018-03-26
        • 1970-01-01
        • 2013-07-21
        • 2010-11-13
        相关资源
        最近更新 更多