[英]Can I compile and run a spring boot jar from inside another spring boot application?
如此快速的澄清是因為我已經閱讀了之前的一些類似問題:
我已經看過https://www.toptal.com/spring-boot/spring-boot-application-programmatic-launch 。 這非常有幫助,但我不確定如何編譯和加載 spring boot 應用程序。
我有一個暗示,這是在 Tomcat TomcatServletWebServerFactory 級別完成的 - 基本上 spring boot“助手”應用程序將觸發 tomcat 加載外部 jar 並部署。 我不是 100% 確定這是否正確。
您不能簡單地在從主 Spring Boot 應用程序中啟動的外部進程中構建和運行輔助 Spring Boot 應用程序嗎?
我剛剛在一個非常簡單的概念驗證中嘗試了這個。 對於此 POC,我創建了兩個虛擬 Spring Boot 應用程序,一個稱為outer
,一個稱為inner
。 后者應該由前者建造和運行。
這是目錄結構(為簡潔起見,在兩個 Gradle 項目中省略Gradle 7.6 Wrapper文件):
├── 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
這兩個settings.gradle
文件都是空的。 這兩個build.gradle
文件也具有相同的內容:
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'
}
“內部”應用程序是Spring Quickstart Guide中的演示應用程序,即inner/src/main/java/com/example/demo/DemoApplication.java
如下所示:
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);
}
}
inner/src/main/resources/application.properties
文件還包含server.port=8081
以便其 Web 服務器運行在與“外部”端口不同的端口上。
這給我們留下了outer/src/main/java/com/example/demo/DemoApplication.java
,它定義了以下(原始)應用程序:
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.";
}
}
您現在可以在outer/
中運行./gradlew bootRun
來啟動“外部”Spring Boot 應用程序——一個 Tomcat Web 服務器。 該服務器響應 POST 請求,啟動“內部”Spring Boot 應用程序的 Gradle 構建,該應用程序也運行該應用程序(一旦構建完成)。 例如,您現在可以嘗試以下交互:
$ 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.
嗯——我知道你想要示例代碼,但我目前沒有時間自己嘗試,這可能不是一個真正的答案——但對於評論來說太大了:
我以前從未做過這樣的事情,但也許我可以在這里提出我的想法 - 如果它很糟糕,請隨意忽略它。
我不知道您的“Main Spring Boot Project”是否使用了 Maven Wrapper,但讓我們假設它使用了。
因此,讓我們嘗試以下概念:
git clone
)在它選擇的某些目錄中。 (你可以用 JGit 或用Runtime.getRuntime().exec("your git command")或你想到的任何東西來做到這一點)gradlew bootJar
時使用 Gradle 一樣)java -jar path/to/your/mySpringBoot.jar fully.qualified.package.Application
的命令來啟動 Spring Boot Jar從概念上講,這聽起來有點您想做嗎? 最后,如果我們考慮一下 - 它是相同的,當您手動簽出您的項目時,構建 JAR 並啟動它 - 不是嗎?
您可以使用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);
}
另請查看文檔 -> Java 編譯器
以 Chriki 的回答為例,我可以從所謂的 OuterApp 運行另一個 spring boot 應用程序。 內部應用程序將在端口號上運行,例如可以是 8081。
import org.springframework.boot.SpringApplication;
public class OuterApplication {
public static void main(String[] args) {
SpringApplication.run(OuterApplication.class, args);
SpringApplication innerApp = new SpringApplication(InnerApplication.class);
innerApp.setDefaultProperties(new HashMap<String, Object>() {{
put("server.port", "8081");
}});
innerApp.run(args);
}
}
您可以通過使用 SpringApplication.run() 方法在主應用程序中啟動內部應用程序來執行此操作。
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
// Start your inner application here
SpringApplication.run(InnerApplication.class, args);
};
}
}
你看過“Maven Invoker”了嗎? 您不僅可以做到,您可以做任何您可以用 Maven 做的事情。
這是我的 github中的示例(它使用 TestContainers,但不是必須的)。
package com.example.demo;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.junit.jupiter.api.Test;
import org.springframework.util.DigestUtils;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.LazyFuture;
import java.io.File;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Future;
public class OCIwithTCCTest {
private static final Future<String> IMAGE_FUTURE = new LazyFuture<>() {
@Override
protected String resolve() {
// Find project's root dir
File cwd;
for (
cwd = new File(".");
!new File(cwd, "mvnw").isFile();
cwd = cwd.getParentFile()
);
// Make it unique per folder (for caching)
var imageName = String.format(
"local/demo-%s:%s",
DigestUtils.md5DigestAsHex(cwd.getAbsolutePath().getBytes()),
System.currentTimeMillis()
);
var properties = new Properties();
properties.put("spring-boot.build-image.imageName", imageName);
properties.put("skipTests", "true");
var request = new DefaultInvocationRequest()
.addShellEnvironment("DOCKER_HOST", DockerClientFactory.instance().getTransportConfig().getDockerHost().toString())
.setPomFile(new File(cwd, "pom.xml"))
.setGoals(List.of("spring-boot:build-image"))
.setMavenExecutable(new File(cwd, "mvnw"))
.setProperties(properties);
InvocationResult invocationResult;
try {
invocationResult = new DefaultInvoker().execute(request);
} catch (MavenInvocationException e) {
throw new RuntimeException(e);
}
if (invocationResult.getExitCode() != 0) {
throw new RuntimeException(invocationResult.getExecutionException());
}
return imageName;
}
};
static final GenericContainer<?> APP = new GenericContainer<>(IMAGE_FUTURE)
.withExposedPorts(8080);
@Test
void containerStartupTest() {
APP.start();
}
}
依賴性:
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-invoker</artifactId>
<version>3.2.0</version>
<scope>test</scope>
</dependency>
此示例存儲庫使用 spring-boot:build-image 將應用程序構建到 OCI 映像中,然后使用 Maven Invoker 在測試中針對該映像運行測試。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.