-修补程序模块和“消失的”源目录

问题描述 投票:2回答:1

当我遇到一个奇怪的问题时,我一直在使用--patch-module向模块添加类。尽管模块本身对其源目录完全不感到困惑,并且似乎不受--patch-module的影响,但使用--patch-module似乎会覆盖getResources中的源(ClassLoader)。尽管只有一个ClassLoader从几个不同的目录加载了所有类,但实际上ClassLoader只剩下一个源目录。

我创建了一个测试结构:

C:\..snip..>tree /f
C:.
+---src
¦   +---home
¦   ¦   +---my
¦   ¦       +---options
¦   ¦               Games.java
¦   ¦               Movie.java
¦   ¦
¦   +---out
¦   ¦   +---my
¦   ¦       +---options
¦   ¦               IceSkating.java
¦   ¦
¦   +---sun.day
¦       ¦   module-info.java
¦       ¦
¦       +---my
¦           +---options
¦                   Cleaning.java
¦                   Options.java

只有一个模块sun.day。其他两个目录是流氓类。它们都共享相同的程序包my.options.class文件的目标结构是相同的。

编译中

javac -d target --module-source-path src --module sun.day
javac -cp target/sun.day -d target/home src/home/my/options/*.java
javac -cp target/sun.day -d target/out src/out/my/options/*.java

仅运行模块

java --module-path target --module sun.day/my.options.Options
java -cp target/sun.day my.options.Options

..将仅列出模块中的类:

package: my.options
  URL = file:/C:/..snip../target/sun.day/my/options/
    classes: [class my.options.Cleaning, class my.options.Options]

全部在类路径上运行

java -cp target/sun.day;target/home;target/out my.options.Options

..将列出所有内容:

package: my.options
  URL = file:/C:/..snip../target/sun.day/my/options
    classes: [class my.options.Cleaning, class my.options.Options]
  URL = file:/C:/..snip../target/home/my/options
    classes: [class my.options.Games, class my.options.Movie]
  URL = file:/C:/..snip../target/out/my/options
    classes: [class my.options.IceSkating]

使用--patch-module运行它以添加流氓类

仅列出home目录:

java --module-path target --patch-module sun.day=target/home;target/out --module sun.day/my.options.Options

package: my.options
  URL = file:/C:/..snip../target/home/my/options/
    classes: [class my.options.Games, class my.options.Movie]

仅列出out目录(似乎--patch-module的第一个参数的目录优先):

java --module-path target --patch-module sun.day=target/out;target/home --module sun.day/my.options.Options

package: my.options
  URL = file:/C:/..snip../target/out/my/options/
    classes: [class my.options.IceSkating]

此仅列出sun.day目录,包括警告:

java --module-path target --patch-module sun.day=target/sun.day;target/out;target/home --module sun.day/my.options.Options

WARNING: module-info.class ignored in patch: target\sun.day

package: my.options
  URL = file:/C:/..snip../target/sun.day/my/options/
    classes: [class my.options.Cleaning, class my.options.Options]

模块对来源说什么?

[我尝试同时使用ModuleModuleFinder)和ResolvedModule来查看是否存在差异:

java --module-path target --patch-module sun.day=target/out;target/home --module sun.day/my.options.Options modules

但是模块本身显然不对从何处加载感到困惑,并且两者之间没有区别(使用Path[]与使用ResolvedModule):

Path[] = [target\home, target\out]

Path[] = [target]
"Path" sun.day is located at:
  file:///C:/..snip../target/sun.day/

Resolved sun.day is located at:
  file:///C:/..snip../target/sun.day/

比较ClassLoader

java --module-path target --patch-module sun.day=target/out;target/home --module sun.day/my.options.Games

即使URLfile:/C:/..snip../target/out/my/options/,也清楚地发现了Games目录中的home类,并且它与ClassLoader具有相同的Options class

this  = jdk.internal.loader.ClassLoaders$AppClassLoader@7907ec20
other = jdk.internal.loader.ClassLoaders$AppClassLoader@7907ec20
 references are identical

源文件

// src\out\my\options\IceSkating.java
package my.options;

public class IceSkating{
  public String toString(){
    return "I'm IceSkating";
  }
}

// src\home\my\options\Movie.java
package my.options;

public class Movie{
  public String toString(){
    return "I'm Movie";
  }
}

// src\home\my\options\Games.java
package my.options;

public class Games{
  public static void main(String[] args) throws Exception{
    new Games().printCompareClassLoader();
  }

  public void printCompareClassLoader(){
    new Options().printCompareClassLoader(this.getClass().getClassLoader());
  }

  public String toString(){
    return "I'm Games";
  }
}

// src\sun.day\my\options\Cleaning.java
package my.options;

class Cleaning{
  public String toString(){
    return "I'm Cleaning";
  }
}

// src\sun.day\my\options\Options.java
package my.options;

import java.util.List;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Arrays;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;

import java.net.URL;
import java.net.URI;

import java.lang.module.ModuleFinder;
import java.lang.module.Configuration;

public class Options {

  public static void main(String[] args) throws Exception{
    System.out.println();

    Options options = new Options();
    options.printPackageLocation("my.options");

    if (args.length > 0) options.findBothModuleLocation();
  }

  public String toString(){
    return "I'm Options";
  }

  void printCompareClassLoader(ClassLoader otherClassLoader){
    System.out.println();
    System.out.println("this  = " + this.getClass().getClassLoader());
    System.out.println("other = " + otherClassLoader);
    if (this.getClass().getClassLoader() == otherClassLoader) {
      System.out.println(" references are identical");
    } else {
      System.out.println(" references are NOT identical");
    }
  }

  void findBothModuleLocation() {
    System.out.println();
    System.out.println("-------");
    System.out.println("------- findBothModuleLocation");

    Path[] paths = {Path.of("target","home"), Path.of("target","out")};
    System.out.println("Path[] = " + Arrays.toString(paths));
    this.findModuleLocation("sun.day",paths);     // nothing

    System.out.println();

    paths = new Path[]{Path.of("target")};
    System.out.println("Path[] = " + Arrays.toString(paths));
    this.findModuleLocation("sun.day",paths);     // target/sun.day/

    System.out.println();

    this.findResolvedModuleLocation("sun.day");   // target/sun.day/

    System.out.println("- end - findBothModuleLocation");
    System.out.println("-------");
    System.out.println();
  }

  void findModuleLocation(String moduleName, Path[] paths){
    ModuleFinder finder = ModuleFinder.of(paths);

    finder.find(moduleName)                          // Optional<ModuleReference>
          .stream()                                  // Stream<ModuleReference>
          .map(ref -> ref.location())                // Stream<Optional<URI>>
          .map(op -> op.orElse(URI.create("N/A")))   // Stream<URI>
          .forEach(uri -> System.out.printf("\"Path\" " + moduleName + " is located at:\n  %s\n", uri));
  }

  void findResolvedModuleLocation(String moduleName){
    Configuration parent = ModuleLayer.boot().configuration();

    parent.findModule(moduleName)                        // Optional<ResolvedModule>
          .stream()                                      // Stream<ResolvedModule>
          .map(resMod -> resMod.reference())             // Stream<ModuleReference>
          .map(ref -> ref.location())                    // Stream<Optional<URI>>
          .map(op -> op.orElse(URI.create("N/A")))       // Stream<URI>
          .forEach(uri -> System.out.printf("Resolved " + moduleName + " is located at:\n  %s\n", uri));
  }

  void printPackageLocation(String packageName) {
      // taken from https://dzone.com/articles/get-all-classes-within-package
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      String path = packageName.replace('.', '/');
      try {
        System.out.println("package: " + packageName);        
        List<File> dirs = new ArrayList<>();
        Enumeration<URL> resources = classLoader.getResources(path);
        while (resources.hasMoreElements()) {
            URL url = resources.nextElement();
            dirs.add(new File(url.getFile()));
            System.out.println("  URL = " + url);
            System.out.println("    classes: " + findClasses(new File(url.getFile()), packageName));
        }
        if (dirs.isEmpty()) System.out.println("No URL found"); 
      } catch (IOException | ClassNotFoundException ex){
        System.out.println("Ooops! printPackageLocation has this ERROR: " + ex);
      }
  }

  private List<Class<?>> findClasses(File directory, String packageName)  throws ClassNotFoundException {
      // taken from https://dzone.com/articles/get-all-classes-within-package
      List<Class<?>> classes = new ArrayList<>();
      if (!directory.exists()) {
          return classes;
      }
      File[] files = directory.listFiles();
      for (File file : files) {
          if (file.isDirectory()) {
              assert !file.getName().contains(".");
              classes.addAll(findClasses(file, packageName + "." + file.getName()));
          } else if (file.getName().endsWith(".class")) {
              classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
          }
      }
      return classes;
  }
}

问题

其他源目录名称在哪里?有没有办法使用包或模块名称同时使用--patch-module查找它们?

java java-module
1个回答
0
投票

我使用Scanning classpath/modulepath in runtime in Java 9的答案,特别是this answer

ModuleReference#open()为您提供了一个ModuleReader,可让您使用ModuleReader#list()列出模块中的资源>

所以诀窍是从ModuleReader获取URI,而不是ModuleReference

// src\sun.day\my\options\Options.java
package my.options;

import java.util.Optional;
import java.util.stream.Collectors;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;

import java.lang.module.ModuleReference;
import java.lang.module.ModuleReader;
import java.lang.module.Configuration;

import java.net.URI;
import java.io.IOException;


public class Options {

  // main
  public static void main(String[] args) throws Exception{
    System.out.println();

    Options options = new Options();

    System.out.println("-- directoryMap -- ");
    options.printMap(options.resolvedModuleSourceMap("sun.day"));
  }

  // toString
  public String toString(){
    return "I'm Options";
  }

  // printMap
  <K,V> void printMap(Map<K,Set<V>> map) {
    for (Map.Entry<K,Set<V>> entry : map.entrySet()) {
      System.out.println(entry.getKey());
      entry.getValue().stream().forEach(c -> System.out.println("  " + c));
    }
  }

  // resolvedModuleSourceMap
  Map<String,Set<Class<?>>> resolvedModuleSourceMap(String moduleName){
    // use ModuleLayer.boot().configuration().modules() if moduleName is empty in a recursive approach.

    Configuration parent = ModuleLayer.boot().configuration();
    ModuleReader moduleReader = null;
    Set<String> resources = null;

    ModuleReference moduleReference =
      parent.findModule(moduleName)              // Optional<ResolvedModule>
            .stream()                            // Stream<ResolvedModule>
            .map(resMod -> resMod.reference())   // Stream<ModuleReference>
            .findAny()                           // Optional<ModuleReference>
            .orElse(null);

    try {
      moduleReader = moduleReference.open();
      resources = moduleReader.list()                                        // Stream<String>
                              .filter(s -> s.contains("."))                  // no directories: "my/", "my/options/"
                              .filter(s -> !s.equals("module-info.class"))   // remove 
                              .collect(Collectors.toSet());
    } catch (IOException ex) {
      System.out.println("Ooops! tryResolvedModuleLocation has this ERROR: " + ex);
    }

//    System.out.println("-- resources -- ");
//    System.out.println(resources);

    Map<String,Set<Class<?>>> directoryMap = new HashMap<>();
    for (String s : resources) {
      try {
            Optional<URI> opt = moduleReader.find(s);
            if (opt != null & opt.isPresent()) {
              String key = opt.stream()
                              .map(uri -> uri.toString())
                              .map(str -> str.replaceAll("("+s+")|(file:/)|(//)",""))
                              .findAny()
                              .get();
              Set<Class<?>> set = directoryMap.get(key);
              if (set == null) {
                set = new HashSet<>();
                directoryMap.put(key,set);
              }
              set.add(Class.forName(s.replace("/",".").replace(".class","")));
            }
          } catch (IOException | ClassNotFoundException ex) {
            System.out.println("Ooops! tryResolvedModuleLocation has this ERROR: " + ex);
          }
    }

//    System.out.println("-- directoryMap -- ");
//    System.out.println(directoryMap);
    return directoryMap;
  }

  // printCompareClassLoader
  void printCompareClassLoader(ClassLoader otherClassLoader){
    System.out.println();
    System.out.println("this  = " + this.getClass().getClassLoader());
    System.out.println("other = " + otherClassLoader);
    if (this.getClass().getClassLoader() == otherClassLoader) {
      System.out.println(" references are identical");
    } else {
      System.out.println(" references are NOT identical");
    }
  }
}

使用--patch-module运行它以添加流氓类

java --module-path target --patch-module sun.day=target/home;target/out --module sun.day/my.options.Options

-- directoryMap --
C:/..snip../target/sun.day/
  class my.options.Options
  class my.options.Cleaning
C:/..snip../target/out/
  class my.options.IceSkating
C:/..snip../target/home/
  class my.options.Movie
  class my.options.Games
© www.soinside.com 2019 - 2024. All rights reserved.