问题描述
Spring Boot 2.3.7、嵌入式 Jetty、JSP 和 WAR 封装。我想在 spring 上下文初始化时显示我的一些静态 HTML 页面。它应该在应用程序启动时和 spring 上下文刷新之前可见。我尝试使用本手册作为示例 https://www.nurkiewicz.com/2015/09/displaying-progress-of-spring.html 但这不起作用。 我需要在jetty初始化时直接启动嵌入式jetty。但是 Spring Boot 仅在上下文刷新时才启动嵌入式码头。 我该怎么做?
解决方法
我创建了一个 Jetty warper 热身课程:
public final class WarmupServer {
private final String contextPath;
private final String displayName;
private final DefaultApplicationArguments arguments;
private final String[] welcomeFiles;
private final Resource baseResource;
private Server server;
public WarmupServer(String contextPath,String displayName,Resource baseResource,String[] welcomeFiles,String... runArgs) {
this.contextPath = StringUtils.defaultIfBlank(contextPath,"/");
this.displayName = StringUtils.defaultIfBlank(displayName,"Warmup");
this.baseResource = ObjectUtils.defaultIfNull(baseResource,Resource.newClassPathResource("/static"));
this.welcomeFiles = ArrayUtils.isEmpty(welcomeFiles) ? new String[]{"html/warmup.html"} : welcomeFiles;
this.arguments = new DefaultApplicationArguments(ArrayUtils.nullToEmpty(runArgs));
}
public Server start() throws Exception {
if (server != null && server.isStarted()) {
throw new IllegalStateException("Server already started");
}
server = new Server();
server.setHandler(buildServletHandler());
final String configPath = parseArg(OPT_CONFIG);
if (StringUtils.isBlank(configPath)) {
throw new RuntimeException(OPT_CONFIG + " argument is not set");
}
final Config config = ConfigUtils.parseFile(new File(configPath),DEFAULT_CONFIG_FILE_NAME);
configureHttpConnector(config);
configureHttpsConnector(config);
server.start();
return server;
}
public void registerWarmupServerStopLifecycle(ConfigurableApplicationContext context) {
context.getBeanFactory()
.registerSingleton(WarmupStopLifecycle.class.getSimpleName(),new WarmupStopLifecycle(server));
}
private ServletContextHandler buildServletHandler() {
final ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.SESSIONS);
handler.addServlet(DefaultServlet.class,"/");
handler.setDisplayName(displayName);
handler.setContextPath(contextPath);
handler.setWelcomeFiles(welcomeFiles);
handler.setBaseResource(baseResource);
return handler;
}
private void configureHttpConnector(Config config) {
final int httpPort = NumberUtils.toInt(parseArg(OPT_HTTP_PORT),config.getInt(OPT_HTTP_PORT));
final ServerConnector connector = new ServerConnector(server);
connector.setPort(httpPort);
server.addConnector(connector);
}
private void configureHttpsConnector(Config config) {
final int httpsPort = NumberUtils.toInt(
parseArg(OPT_HTTPS_PORT),config.getInt(OPT_HTTPS_PORT));
final String keyStorePath = StringUtils.defaultIfBlank(
parseArg(OPT_KEYSTORE_FILE),config.getString(OPT_KEYSTORE_FILE));
final boolean sslEnabled = StringUtils.isNotBlank(keyStorePath)
&& Files.isReadable(Paths.get(keyStorePath));
if (sslEnabled) {
final HttpConfiguration configuration = new HttpConfiguration();
configuration.setSecurePort(httpsPort);
configuration.setSecureScheme(HTTPS_SCHEME);
final ServerConnector httpsConnector = new HttpsConnector()
.createConnector(server,configuration,config.getConfig(JETTY_HTTPS_CONFIG),httpsPort);
server.addConnector(httpsConnector);
}
}
private String parseArg(String optionName) {
final List<String> values = arguments.getOptionValues(optionName);
return CollectionUtils.isEmpty(values) ? StringUtils.EMPTY : values.get(0);
}
public static WarmupServer start(String contextPath,String... runArgs) throws Exception {
final WarmupServer server = new WarmupServer(contextPath,displayName,baseResource,welcomeFiles,runArgs);
server.start();
return server;
}
}
此包装器解析命令行参数并使用提供的命令行参数创建 Jetty 处理程序和 HTTP 和(或)HTTPS 连接器。
以及简单的 Spring Lifecycle 实现类:
@RequiredArgsConstructor
class WarmupStopLifecycle implements SmartLifecycle {
private static final Logger logger = LogManager.getFormatterLogger();
private final Server warmupServer;
private volatile boolean isRunning;
@Override
public void start() {
try {
warmupServer.stop();
isRunning = true;
} catch (Exception e) {
logger.error("Failed to stop warmup server",e);
throw new RuntimeException("Failed to stop warmup server",e);
}
}
@Override
public void stop() {
}
@Override
public boolean isRunning() {
return isRunning;
}
/**
* Returns phase of this lifecycle.
* A phase MUST be before the Spring web server starts.
* See {@code org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle} phase.
*/
@Override
public int getPhase() {
return Integer.MAX_VALUE - 2;
}
}
所以使用这个:
@SpringBootApplication
public class SpringApplication {
public static void main(String[] args) throws Exception {
final WarmupServer warmupServer = WarmupServer.start(
"/my_context_path","My Warmup server handler",args);
new SpringApplicationBuilder()
.sources(SpringApplication.class)
.initializers(warmupServer::registerWarmupServerStopLifecycle)
.run(args);
}
}
WarmupServer
在应用程序运行后立即启动,并在启动 Spring 的 Web 服务器之前停止。