[英]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.