在运行时编译Java类,并具有对嵌套jar的依赖关系

时间:2018-12-26 19:47:15

标签: spring-boot java-compiler-api runtime-compilation javacompiler

在spring-boot应用中,我正在运行时执行以下操作:

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

我的代码基于this post,并且在运行时编译生成的类时遇到问题。在IDE中运行时,编译工作正常,但是从spring-boot jar运行时,编译失败,提示缺少符号或某些软件包不存在。我正在编译的类与位于\BOOT-INF\lib\下的jar中的其他类有依赖关系,看来编译器无法使用现有的classLoader加载这些类。

我遵循了this post,该问题本来是要解决此特定问题的,但是我却从方法中得到了UnsupportedOperationException

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

在给定here的情况下,我还遇到了另一种可能的解决方案,但是对于完整的实现,我还是不清楚。

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

更新: 我当前正在使用Java 10.0.2。

1 个答案:

答案 0 :(得分:1)

尽管您没有明确提及它,但我认为您正在使用modules(JDK 9+)运行Java版本,但是您遵循的指南适用于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!),并像使用平台类一样将其委派给标准文件管理器先前版本中的路径:

@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 Boot类:

通过查找'!'的最后一个索引来获取jarUri。在每个packageFolderURL中,如Taeyun Kim's comment中那样,而不是原始示例中的第一个。

 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 jars(在BOOT-INF/lib下)中的类,然后由spring boot jar URI handler进行解析,该in this answer的注册方式类似{{3}}所述的方法。 URI处理应该只通过Spring Boot自动发生。