当我遇到一个奇怪的问题时,我一直在使用--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]
仅列出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]
[我尝试同时使用Module
(ModuleFinder
)和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/
java --module-path target --patch-module sun.day=target/out;target/home --module sun.day/my.options.Games
即使URL
是file:/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
查找它们?
我使用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