So quick clarifications because I have read some of the previous similar questions:
- I'm looking to compile and run a spring boot codebase from a running spring application.
- I'm not looking to nest or package multiple spring boot jars inside one. The second spring boot codebase is outside. Maybe even on github.
I have already looked at https://www.toptal.com/spring-boot/spring-boot-application-programmatic-launch. It was super helpful, but I'm not sure how to compile and load a spring boot application.
I have an inkling that this is done at the Tomcat TomcatServletWebServerFactory level - basically the spring boot "helper" application will trigger tomcat to load the external jar and deploy. I'm not 100% sure if this is correct.
CodePudding user response:
Can’t you simply build and run your secondary Spring Boot applications in external processes that are started from within your primary Spring Boot application?
I’ve just tried this in a very simple proof-of-concept. For this POC, I have created two dummy Spring Boot applications, one called outer
and one called inner
. The latter is supposed to be built and run by the former.
Here’s the directory structure (ommitting Gradle 7.6 Wrapper files in each of the two Gradle projects for brevity):
├── inner
│ ├── build.gradle
│ ├── settings.gradle
│ └── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── demo
│ │ └── DemoApplication.java
│ └── resources
│ └── application.properties
└── outer
├── build.gradle
├── settings.gradle
└── src
└── main
└── java
└── com
└── example
└── demo
└── DemoApplication.java
The two settings.gradle
files are both empty. The two build.gradle
files have the same content, too:
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.6'
id 'io.spring.dependency-management' version '1.1.0'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}
The “inner” application is the demo applicatation from the Spring Quickstart Guide, i.e., inner/src/main/java/com/example/demo/DemoApplication.java
looks as follows:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("/hello")
public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
return String.format("Hello %s!", name);
}
}
The inner/src/main/resources/application.properties
file additionally contains server.port=8081
so that its web server is run on a different port than the one of “outer”.
That leaves us with outer/src/main/java/com/example/demo/DemoApplication.java
which defines the following (crude) application:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.IOException;
@SpringBootApplication
@RestController
public class DemoApplication {
private Process otherAppProcess = null;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@PostMapping("/run")
public String run() throws IOException, InterruptedException {
synchronized (this) {
if (otherAppProcess != null) {
stop();
}
var processBuilder = new ProcessBuilder("./gradlew", "bootRun");
processBuilder.directory(new File("../inner"));
otherAppProcess = processBuilder.start();
}
return "Done.";
}
@PostMapping("/stop")
public String stop() {
synchronized (this) {
if (otherAppProcess != null) {
otherAppProcess.destroy();
otherAppProcess = null;
}
}
return "Ok.";
}
}
You can now run ./gradlew bootRun
in outer/
to start the “outer” Spring Boot application – a Tomcat web server. That server reacts to a POST request wich starts a Gradle build of the “inner” Spring Boot application which also runs that application (once the build is complete). For example, you can now try the following interaction:
$ curl -X GET http://localhost:8081/hello
curl: (7) Failed to connect to localhost port 8081 after 0 ms: Connection refused
$ curl -X POST http://localhost:8080/run
Done.
$ curl -X GET http://localhost:8081/hello
Hello World!
$ curl -X POST http://localhost:8080/stop
Ok.
CodePudding user response:
Hmm - I know you wanted sample Code, but I lack the time currently to try it out by myself and this here might not be a real answer - but too big for a comment:
I never done something like this before, but maybe I could throw my idea in here - if it sucks, feel free to ignore it.
I don't know if your "Main Spring Boot Project" uses the Maven Wrapper, but lets assume it does.
So let's try the following concept:
- You start your "Main" Spring Boot Application, which actually could Build and Start multiple Spring Boot Applications.
- The Main Spring Boot Application checksout X different Spring Boot Applications from GitHub / GitLab whatever (with
git clone
) in some directories of its choice. (you could do this with JGit or with Runtime.getRuntime().exec("your git command") or whatever comes in your mind) - Knowing that the Maven Wrapper exists in this folder, you could basically build the Spring Boot JAR in the target Folder, (or like with Gradle when you do
gradlew bootJar
) - After the "Shell Command" did execute successfully, you could start the Spring Boot Jar by executing something like
java -jar path/to/your/mySpringBoot.jar fully.qualified.package.Application
Does it sound conceptually somewhat you want to do? In the end, if we think about it - it is the same, when you checkout your Project manually, build the JAR and start it - ain't it?
CodePudding user response:
You can use JavaCompiler
.
public static void main(String[] args) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjectsFromStrings(Arrays.asList("src/main/java/com/example/app2/Application.java"));
compiler.getTask(null, fileManager, null, null, null, fileObjects).call();
fileManager.close();
// you can run jar file from this host application
String[] newArgs = {"--spring.config.name=externalApp", "--spring.config.additional-location=file:/etc/externalApp/"};
SpringApplication.run(com.example.externalApp.Application.class, newArgs);
}
Also take a look at the documentation -> Java Compiler