在运行时编译 java 类,依赖于嵌套的 jar [英] Compile java class in runtime with dependencies to nested jar

查看:47
本文介绍了在运行时编译 java 类,依赖于嵌套的 jar的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In a spring-boot app I'm doing the following in runtime:

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

我的代码基于 这篇文章 并且在运行时编译我生成的类时遇到问题.在 IDE 编译中运行时可以正常工作,但是当从 spring-boot jar 编译运行时失败,说缺少符号或某些包不存在.我正在编译的类依赖于位于 \BOOT-INF\lib\ 下的 jar 中的其他类,并且编译器似乎无法使用现有的 classLoader 加载这些类.

I've based my code on this post and got a problem compiling my generated class in runtime. When running in the IDE compilation works just fine but when runing from a spring-boot jar compilation fails saying symbols are missing or some package does not exist. The class I'm compiling has dependencies to other classes that reside in a jar under \BOOT-INF\lib\ and it seems the compiler fails to load those classes using the existing classLoader.

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

I've followed this post which suppose to address this specific problem but I got UnsupportedOperationException coming from method

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

接口JavaFileManager.

我在 此处 遇到了另一种可能的解决方案,但我不太清楚完整的实现.

I've encountered another possible solution given here but I'm not clear exactly with the full implementation.

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

This seems like a well known issue when compiling a class in runtime, is there any clear solution for that?

更新:我目前使用的是 java 10.0.2.

Update: I'm currently using java 10.0.2.

推荐答案

虽然您没有明确提到它,但我认为您正在使用 modules (JDK 9+),但您所遵循的指南适用于从 Java 6 开始的早期版本.这是为什么您会收到有关不支持的 listLocationsForModules 的错误消息,因为 JDK 开发人员使用会抛出 UnsupportedOperationException 的默认方法改装了 FileManager.

Although you haven't mentioned it explicitly I think you are running a version of Java with modules (JDK 9+), but the guides you have been following are for earlier versions starting from Java 6. This is why you are getting the error about unsupported listLocationsForModules because the JDK developers retrofitted the FileManager with with default methods that throw UnsupportedOperationException.

如果您实际上不想使用大于 8 的 Java 版本,我会坚持使用 JDK8,它会容易得多!

If you don't actually want to use a version of Java greater than 8, I would stick with JDK8 it will be much easier!

我将继续假设您确实想使用 Java 9 及更高版本(在 Java 11 中测试了我的代码):

I'll continue assuming you do want to use Java 9 and above (tested my code in Java 11) however:

为了处理模块,你的文件管理器委托给标准文件管理器就足够了:

For handling modules it is sufficient your file manager to delegate to the standard file manager:

@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!)并像对平台类一样委托给标准文件管理器以前版本中的路径:

I also found it necessary to modify Atamur's code to check explicitly for the base java module (so that we can resolve java.lang in Java 9+!) and delegate to the standard file manager as you would have for the platform class path in previous versions:

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

}

更新

其他几点:

Updates

Some other points:

提取嵌入的 Spring Boot 类:

Extracting embedded spring boot classes:

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

Get the jarUri by looking for the last index of '!' in each packageFolderURL as in Taeyun Kim's comment and not the first as in the original example.

 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 jar URI 处理程序,其注册方式与在这个答案中.URI 处理应该通过 spring boot 自动发生.

This allows package PackageInternalsFinder to return CustomJavaFileObject with full URIs to classes in embedded spring jars (under BOOT-INF/lib) which are then resolved with spring boot jar URI handler which is registered in similar way to as explained in this answer. The URI handling should just happen automatically through spring boot.

这篇关于在运行时编译 java 类,依赖于嵌套的 jar的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆