Our applications contain multiple main classes via dependent jar library. (e.g.: a dependent jar contains simple tool app to run db upgrade.)
Currently, [Jar|War]Launcher
reads Start-Class
from manifest file and simply fails if it doesn't exist.
Since [Jar|War]Launcher
is used for fat jar, java -jar
method cannot run different main class when there are other main classes available in the jar(maybe nested jar).
Workaround is to use PropertiesLauncher
with java -cp
.
java -cp app.jar -Dloader.main=com.example.AnotherApplication org.springframework.boot.loader.PropertiesLauncher
Similar to jarmode
, it would be nice to allow [Jar|War]Launcher
to take special system property to allow overriding start-class.
e.g.:
java -jar -Dstart-class=com.example.AnotherApplication app.jar
This way, user can just use java -jar
instead of java -cp ...
with PropertiesLauncher
.
Since many users simply use java -jar
, it is convenient and discoverable option than PeropertiesLauncher
.
Comment From: wilkinsona
Thanks for the suggestion.
Rather than supporting this in the jar and war launchers, which would blur the lines between them and the PropertiesLauncher
, I think it would be better handled in your application's main method. You could use a command line argument or system property of your choosing to control the behaviour of your application. For example, you could run different applications based on a system property:
public static void main(String[] args) {
String appMode = System.getProperty("appMode");
if (appMode.equals("alpha")) {
SpringApplication.run(AlphaApplication.class, args);
}
else if (appMode.equals("bravo")) {
SpringApplication.run(BravoApplication.class, args);
}
else if (appMode.equals("charlie")) {
SpringApplication.run(CharlieApplication.class, args);
}
else {
System.err.println("Unrecognized appMode '" + appMode + "'");
System.exit(1);
}
}
Another option that would give more control over exactly how each mode behaves would be to delegate to a main method:
public static void main(String[] args) {
String appMode = System.getProperty("appMode");
if (appMode.equals("alpha")) {
AlphaApplication.main(args);
}
else if (appMode.equals("bravo")) {
BravoApplication.main(args);
}
else if (appMode.equals("charlie")) {
CharlieApplication.main(args);
}
else {
System.err.println("Unrecognized appMode '" + appMode + "'");
System.exit(1);
}
}
An advantage of this approach over a system property that controls the start class is that users don't need to know the fully qualified name of each main class. Instead, they can use values that are shorter, easier to remember, and that do not expose something that could be considered an implementation detail of the app.
Comment From: ttddyy
Thanks for suggestion.
Since I provide the launcher as library, I don't have reference to concrete application classes. So, I just simply put this class on top of spring-boot's jar/war launcher in my library:
public class MyLauncher {
public static void main(String[] args) throws Exception {
String mainClassName = System.getProperty("launch");
if (mainClassName == null) {
System.err.println("System property 'launch' is not defined.");
System.exit(1);
}
// Launcher#launch from spring-boot put its classloader(LaunchedURLClassLoader)
// to current context classloader. Therefore, it is available in this thread and
// MainMethodRunner uses it to launch the application, which allows to read
// resources from executable jar file.
MainMethodRunner mainMethodRunner = new MainMethodRunner(mainClassName, args);
mainMethodRunner.run();
}
}
To use this launcher, Start-Class
entry needs to point to this class. Then, spring-boot's jar/war launcher simply calls this with its classloader and here just dispatches the main class based on the launch
parameter.
This implementation has limitation that whenever calling java -jar
command launch
parameter is always required.
So, I put this in library, but whether to use this or other mechanism is left to application teams's decision.
This is my side update and fyi if anybody reading this issue might want to use this impl in future.
Comment From: philwebb
@ttddyy You might want to look at the jarmode support we're adding to 2.3. It might help with your use-cae. Some docs for how we use it are here: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#writing-the-dockerfile. The org.springframework.boot.loader.jarmode.JarMode
interface is the main entrypoint.
Comment From: ttddyy
Thanks @philwebb
Yeah, I can provide a custom impl since JarMode
handling logic is loaded from spring factories.
Thanks for the idea. I'll explore it when I get a chance.
The new layertools jarmode would unify our docker layering strategy which is currently vary project by project. It will be another task for me to provide layertools guideline to application teams when boot 2.3 is released. We benefit updates from this area. So, thanks!!