在运行时编译Java类,并依赖于嵌套的jar



在Spring Boot应用程序中,我在运行时执行以下操作:

  1. 生成Java类
  2. 编译它
  3. 使用反射访问已编译类的一些静态字段

我的代码基于这篇文章,在运行时编译生成的类时遇到了问题。当在IDE中运行时,编译工作正常,但当从Spring Boot jar中运行时编译失败,表示符号丢失或某些包不存在。我正在编译的类与BOOT-INFlib下的jar中的其他类有依赖关系,而且编译器似乎无法使用现有的类加载器加载这些类。

我关注了这篇文章,它试图解决这个特定的问题,但我得到了来自方法的UnsupportedOperationException

default Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
throw new UnsupportedOperationException();
}

接口CCD_ 3。

我遇到了这里给出的另一个可能的解决方案,但我不清楚完整的实现。

在运行时编译类时,这似乎是一个众所周知的问题,对此有明确的解决方案吗?

我目前使用的是Java 10.0.2。

虽然您没有明确提到它,但我认为您运行的是带有模块的Java版本(JDK 9+),但您一直遵循的指南适用于从Java 6开始的早期版本。这就是为什么您会得到关于不受支持的listLocationsForModules的错误,因为JDK开发人员使用抛出UnsupportedOperationException的默认方法对FileManager进行了改装。

如果你实际上不想使用大于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!),并将其委托给标准文件管理器,就像您在以前版本中对平台类路径所做的那样:

@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();
}

更新

其他几点:

提取嵌入式弹簧引导类:

通过查找"!"的最后一个索引来获取jarUri在每个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将带有完整URI的CustomJavaFileObject返回到嵌入的spring-jar(在BOOT-INF/lib下)中的类,然后用spring-boot-jarURI处理程序解析这些类,该处理程序以与本答案中解释的类似的方式注册。URI处理应该通过spring引导自动进行。

最新更新