在运行时编译 Java class 与嵌套 jar 的依赖关系
Compile Java class in runtime with dependencies to nested jar
在 Spring 启动应用程序中,我在运行时执行以下操作:
- 正在生成 Java class
- 编译它
- 使用反射访问已编译的 class 的一些静态字段。
我的代码基于 this post,但在运行时编译生成的 class 时遇到问题。当 IDE 编译中的 运行 工作正常,但是当 Spring 中的 运行 引导 jar 编译失败时说符号丢失或某些包不存在。我正在编译的 class 依赖于驻留在 \BOOT-INF\lib\
下的 jar 中的其他 classes,编译器似乎无法使用现有 classes 加载这些 classes =43=] 装载机.
我已经按照 this post 来解决这个特定问题,但我得到 UnsupportedOperationException
来自方法
default Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
throw new UnsupportedOperationException();
}
接口JavaFileManager
。
我遇到了另一种可能的解决方案here,但我不太清楚完整的实现。
这似乎是在运行时编译 class 时的一个众所周知的问题,是否有明确的解决方案?
我目前正在使用 Java 10.0.2.
虽然你没有明确提到它,但我认为你是 运行 的 Java 版本 modules (JDK 9+),但指导你一直在关注从 Java 6 开始的早期版本。这就是为什么您会收到有关不受支持的 listLocationsForModules
的错误的原因,因为 JDK 开发人员使用默认方法改进了 FileManager
抛出 UnsupportedOperationException
.
如果您实际上不想使用大于 8 的 Java 版本,我会坚持使用 JDK8,这样会容易得多!
我将继续假设您确实想使用 Java 9 及更高版本(在 Java 11 中测试了我的代码)但是:
对于处理模块,您的文件管理器委托给标准文件管理器就足够了:
@Override
public Location getLocationForModule(Location location, String moduleName) throws IOException {
return standardFileManager.getLocationForModule(location, moduleName);
}
@Override
public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException {
return standardFileManager.getLocationForModule(location, fo);
}
@Override
public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
return standardFileManager.listLocationsForModules(location);
}
@Override
public String inferModuleName(Location location) throws IOException {
return standardFileManager.inferModuleName(location);
}
我还发现有必要修改 Atamur 的代码以显式检查基础 java 模块(以便我们可以在 Java 9+ 中解析 java.lang!)并委托给标准文件管理器,就像以前版本中的平台 class 路径一样:
@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
boolean baseModule = location.getName().equals("SYSTEM_MODULES[java.base]");
if (baseModule || location == StandardLocation.PLATFORM_CLASS_PATH) { // **MODIFICATION CHECK FOR BASE MODULE**
return standardFileManager.list(location, packageName, kinds, recurse);
} else if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
if (packageName.startsWith("java") || packageName.startsWith("com.sun")) {
return standardFileManager.list(location, packageName, kinds, recurse);
} else { // app specific classes are here
return finder.find(packageName);
}
}
return Collections.emptyList();
}
更新
其他几点:
提取嵌入式 spring 启动 classes:
通过查找 '!' 的最后一个索引获取 jarUri在 Taeyun Kim's comment 中的每个 packageFolderURL 中,而不是原始示例中的第一个。
private List<JavaFileObject> processJar(URL packageFolderURL) {
List<JavaFileObject> result = new ArrayList<JavaFileObject>();
try {
// Replace:
// String jarUri = packageFolderURL.toExternalForm().split("!")[0];
// With:
String externalForm = packageFolderURL.toExternalForm();
String jarUri = externalForm.substring(0, externalForm.lastIndexOf('!'));
JarURLConnection jarConn = (JarURLConnection) packageFolderURL.openConnection();
String rootEntryName = jarConn.getEntryName();
int rootEnd = rootEntryName.length()+1;
// ...
这允许包 PackageInternalsFinder
到 return CustomJavaFileObject
具有到 classes 的完整 URI 在嵌入式 spring 罐子中(在 BOOT-INF/lib
下)然后用 spring boot jar URI handler which is registered in similar way to as explained in this answer 解决。 URI 处理应该通过 spring boot.
自动发生
在 Spring 启动应用程序中,我在运行时执行以下操作:
- 正在生成 Java class
- 编译它
- 使用反射访问已编译的 class 的一些静态字段。
我的代码基于 this post,但在运行时编译生成的 class 时遇到问题。当 IDE 编译中的 运行 工作正常,但是当 Spring 中的 运行 引导 jar 编译失败时说符号丢失或某些包不存在。我正在编译的 class 依赖于驻留在 \BOOT-INF\lib\
下的 jar 中的其他 classes,编译器似乎无法使用现有 classes 加载这些 classes =43=] 装载机.
我已经按照 this post 来解决这个特定问题,但我得到 UnsupportedOperationException
来自方法
default Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
throw new UnsupportedOperationException();
}
接口JavaFileManager
。
我遇到了另一种可能的解决方案here,但我不太清楚完整的实现。
这似乎是在运行时编译 class 时的一个众所周知的问题,是否有明确的解决方案?
我目前正在使用 Java 10.0.2.
虽然你没有明确提到它,但我认为你是 运行 的 Java 版本 modules (JDK 9+),但指导你一直在关注从 Java 6 开始的早期版本。这就是为什么您会收到有关不受支持的 listLocationsForModules
的错误的原因,因为 JDK 开发人员使用默认方法改进了 FileManager
抛出 UnsupportedOperationException
.
如果您实际上不想使用大于 8 的 Java 版本,我会坚持使用 JDK8,这样会容易得多!
我将继续假设您确实想使用 Java 9 及更高版本(在 Java 11 中测试了我的代码)但是:
对于处理模块,您的文件管理器委托给标准文件管理器就足够了:
@Override
public Location getLocationForModule(Location location, String moduleName) throws IOException {
return standardFileManager.getLocationForModule(location, moduleName);
}
@Override
public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException {
return standardFileManager.getLocationForModule(location, fo);
}
@Override
public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
return standardFileManager.listLocationsForModules(location);
}
@Override
public String inferModuleName(Location location) throws IOException {
return standardFileManager.inferModuleName(location);
}
我还发现有必要修改 Atamur 的代码以显式检查基础 java 模块(以便我们可以在 Java 9+ 中解析 java.lang!)并委托给标准文件管理器,就像以前版本中的平台 class 路径一样:
@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
boolean baseModule = location.getName().equals("SYSTEM_MODULES[java.base]");
if (baseModule || location == StandardLocation.PLATFORM_CLASS_PATH) { // **MODIFICATION CHECK FOR BASE MODULE**
return standardFileManager.list(location, packageName, kinds, recurse);
} else if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
if (packageName.startsWith("java") || packageName.startsWith("com.sun")) {
return standardFileManager.list(location, packageName, kinds, recurse);
} else { // app specific classes are here
return finder.find(packageName);
}
}
return Collections.emptyList();
}
更新
其他几点:
提取嵌入式 spring 启动 classes:
通过查找 '!' 的最后一个索引获取 jarUri在 Taeyun Kim's comment 中的每个 packageFolderURL 中,而不是原始示例中的第一个。
private List<JavaFileObject> processJar(URL packageFolderURL) {
List<JavaFileObject> result = new ArrayList<JavaFileObject>();
try {
// Replace:
// String jarUri = packageFolderURL.toExternalForm().split("!")[0];
// With:
String externalForm = packageFolderURL.toExternalForm();
String jarUri = externalForm.substring(0, externalForm.lastIndexOf('!'));
JarURLConnection jarConn = (JarURLConnection) packageFolderURL.openConnection();
String rootEntryName = jarConn.getEntryName();
int rootEnd = rootEntryName.length()+1;
// ...
这允许包 PackageInternalsFinder
到 return CustomJavaFileObject
具有到 classes 的完整 URI 在嵌入式 spring 罐子中(在 BOOT-INF/lib
下)然后用 spring boot jar URI handler which is registered in similar way to as explained in this answer 解决。 URI 处理应该通过 spring boot.