Open z-950 opened 4 years ago
环境:openJDK 13、Kotlin 1.3.72、Vert.x 3.9.x
遇到需要重写Launcher进行固定配置的情况,参考官方例子。
由于个人主要使用Kotlin进行编写,所以重写Launcher类自然也会用Kotlin。下面介绍遇到的问题。
对于Kotlin,main可以单独定义,也可以使用伴生对象在类里面定义。
Launcher类是启动类,所以Vert.x的Launcher类中定义了main方法。重写时,也需要定义自己main方法。
自己的Launcher写在MyLauncher.kt中。
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)
Accidental override: The following declarations have the same JVM signature (main([Ljava/lang/String;)V)
经过搜索,了解到可以通过@JvmName注解改变重写的方法的名字来解决该错误,但此为main函数,不可修改名字,故无解。
@JvmName
fun main(args: Array<String>) { MyLauncher().dispatch(args) } class MyLauncher : io.vertx.core.Launcher() {}
不会报错。来看一下能否运行。
配置build.gradle的mainClass为自己的Launcher:vertx { launcher = "com.example.starter.MyLauncher" }
vertx { launcher = "com.example.starter.MyLauncher" }
此处使用了io.vertx.vertx-plugin这个插件。只用java插件时,应该配置mainClassName字段
io.vertx.vertx-plugin
java
mainClassName
编译并运行。报错:java.lang.ClassNotFoundException: com.example.starter.Launcher
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" }。再次编译运行,没有报错。
vertx { launcher = "com.example.starter.MyLauncherKt" }
答案是不可行,标题已经说明了。
至于为什么不可行,还是出于方便角度考虑的。
对于Vert.x,一个jar里还得包括mainVerticle用于启动,mainVerticle中才是应用的启动逻辑。
mainVerticle可以在build.gradle里设置,放到manifest里。这样我们可以直接运行jar,否则还得传入mainVerticle参数。
首先说明,传入mainVerticle的麻烦之处(对于微服务架构):
手动配置参数是容易忘记的,毫无意义,不如将其自动化方便。
在dispatch所在的类io.vertx.core.impl.launcher.VertxCommandLauncher中,有如下函数:
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。
attributes.getValue("Main-Class")
MyLauncherKt
而main.getClass().getName()获取到的则是运行时的Launcher的class,也就是MyLauncher。
main.getClass().getName()
MyLauncher
所以,单独定义main函数,并且配置正确的情况下,Vert.x的Launcher是无法获取到manifest中的mainVerticle的。
目前找不到此做法。
如果使用@file:JvmName("MyLauncher")强制输出为MyLauncher.class,则会报错:Duplicate JVM class name 'com/example/starter/MyLauncher' generated from: package-fragment com.example.starter, MyLauncher
@file:JvmName("MyLauncher")
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里,相当于没用。
@file:JvmName("Launcher")
综上,解决办法是,使用Java重写Launcher和其中的main函数。
看到有资料说main函数无法被重写(override),实践了一下,其实是可以的。
遇到需要重写Launcher进行固定配置的情况,参考官方例子。
由于个人主要使用Kotlin进行编写,所以重写Launcher类自然也会用Kotlin。下面介绍遇到的问题。
main函数的编写
对于Kotlin,main可以单独定义,也可以使用伴生对象在类里面定义。
Launcher类是启动类,所以Vert.x的Launcher类中定义了main方法。重写时,也需要定义自己main方法。
自己的Launcher写在MyLauncher.kt中。
使用伴生对象的方式定义main函数
上面的写法会报错:
Accidental override: The following declarations have the same JVM signature (main([Ljava/lang/String;)V)
经过搜索,了解到可以通过
@JvmName
注解改变重写的方法的名字来解决该错误,但此为main函数,不可修改名字,故无解。单独定义main函数
不会报错。来看一下能否运行。
配置build.gradle的mainClass为自己的Launcher:
vertx { launcher = "com.example.starter.MyLauncher" }
编译并运行。报错:
java.lang.ClassNotFoundException: com.example.starter.Launcher
看一下build/classes里面有什么:
具体进去看一下,发现main方法放到了MyLauncher.class里。修改一下mainClass的位置:
vertx { launcher = "com.example.starter.MyLauncherKt" }
。再次编译运行,没有报错。单独定义main函数可行吗
答案是不可行,标题已经说明了。
至于为什么不可行,还是出于方便角度考虑的。
mainVerticle设置
对于Vert.x,一个jar里还得包括mainVerticle用于启动,mainVerticle中才是应用的启动逻辑。
mainVerticle可以在build.gradle里设置,放到manifest里。这样我们可以直接运行jar,否则还得传入mainVerticle参数。
首先说明,传入mainVerticle的麻烦之处(对于微服务架构):
手动配置参数是容易忘记的,毫无意义,不如将其自动化方便。
mainVerticle如何被获取
在
dispatch
所在的类io.vertx.core.impl.launcher.VertxCommandLauncher
中,有如下函数:其中
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函数。