z-950 / blog

This is a blog for myself
0 stars 0 forks source link

Vert.x中不能使用Kotlin重写Launcher类 #3

Open z-950 opened 4 years ago

z-950 commented 4 years ago

环境:openJDK 13、Kotlin 1.3.72、Vert.x 3.9.x

遇到需要重写Launcher进行固定配置的情况,参考官方例子

由于个人主要使用Kotlin进行编写,所以重写Launcher类自然也会用Kotlin。下面介绍遇到的问题。

main函数的编写

对于Kotlin,main可以单独定义,也可以使用伴生对象在类里面定义。

Launcher类是启动类,所以Vert.x的Launcher类中定义了main方法。重写时,也需要定义自己main方法。

自己的Launcher写在MyLauncher.kt中。

使用伴生对象的方式定义main函数

class MyLauncher : io.vertx.core.Launcher() {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            MyLauncher().dispatch(args)
        }
    }
}

上面的写法会报错:Accidental override: The following declarations have the same JVM signature (main([Ljava/lang/String;)V)

经过搜索,了解到可以通过@JvmName注解改变重写的方法的名字来解决该错误,但此为main函数,不可修改名字,故无解。

单独定义main函数

fun main(args: Array<String>) {
    MyLauncher().dispatch(args)
}

class MyLauncher : io.vertx.core.Launcher() {}

不会报错。来看一下能否运行。

配置build.gradle的mainClass为自己的Launcher:vertx { launcher = "com.example.starter.MyLauncher" }

此处使用了io.vertx.vertx-plugin这个插件。只用java插件时,应该配置mainClassName字段

编译并运行。报错:java.lang.ClassNotFoundException: com.example.starter.Launcher

看一下build/classes里面有什么:

+-- com.example.starter
    +-- MyLauncherKt.class
    +-- MyLauncher.class

具体进去看一下,发现main方法放到了MyLauncher.class里。修改一下mainClass的位置:vertx { launcher = "com.example.starter.MyLauncherKt" }。再次编译运行,没有报错。

单独定义main函数可行吗

答案是不可行,标题已经说明了。

至于为什么不可行,还是出于方便角度考虑的。

mainVerticle设置

对于Vert.x,一个jar里还得包括mainVerticle用于启动,mainVerticle中才是应用的启动逻辑。

mainVerticle可以在build.gradle里设置,放到manifest里。这样我们可以直接运行jar,否则还得传入mainVerticle参数。

首先说明,传入mainVerticle的麻烦之处(对于微服务架构):

  1. 使用ide进行调试时,每个微服务启动的task都需要再传入mainVerticle参数
  2. mainVerticle名字修改后,需要手动修改传入的参数

手动配置参数是容易忘记的,毫无意义,不如将其自动化方便。

mainVerticle如何被获取

dispatch所在的类io.vertx.core.impl.launcher.VertxCommandLauncher中,有如下函数:

  private String getFromManifest(String key) {
    try {
      Enumeration<URL> resources = RunCommand.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
      while (resources.hasMoreElements()) {
        try (InputStream stream = resources.nextElement().openStream()) {
          Manifest manifest = new Manifest(stream);
          Attributes attributes = manifest.getMainAttributes();
          String mainClass = attributes.getValue("Main-Class");
          if (main.getClass().getName().equals(mainClass)) {
            String value = attributes.getValue(key);
            if (value != null) {
              return value;
            }
          }
        }
      }
    } catch (IOException e) {
      throw new IllegalStateException(e.getMessage());
    }
    return null;
  }

其中attributes.getValue("Main-Class")获取到的是build.gradle中配置的mainClass,也就是MyLauncherKt

main.getClass().getName()获取到的则是运行时的Launcher的class,也就是MyLauncher

所以,单独定义main函数,并且配置正确的情况下,Vert.x的Launcher是无法获取到manifest中的mainVerticle的。

改变main函数编译后的文件名

目前找不到此做法。

如果使用@file:JvmName("MyLauncher")强制输出为MyLauncher.class,则会报错:Duplicate JVM class name 'com/example/starter/MyLauncher' generated from: package-fragment com.example.starter, MyLauncher

修改为@file:JvmName("Launcher")可以解决此错误,但Launcher.class里只有main函数,并且MyLauncher类依然在MyLauncher.class里,相当于没用。

总结

综上,解决办法是,使用Java重写Launcher和其中的main函数。

看到有资料说main函数无法被重写(override),实践了一下,其实是可以的。