Add property 'server.tomcat.use-apr' to control Tomcat's APR
The property's default depends on the Java version. On Java < 24, it defaults to WHEN_AVAILABLE, on Java >=24 it defaults to NEVER. Closes gh-44033
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,6 +34,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.boot.convert.DurationUnit;
|
||||
import org.springframework.boot.system.JavaVersion;
|
||||
import org.springframework.boot.web.server.Compression;
|
||||
import org.springframework.boot.web.server.Cookie;
|
||||
import org.springframework.boot.web.server.Http2;
|
||||
@@ -513,6 +514,12 @@ public class ServerProperties {
|
||||
*/
|
||||
private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8);
|
||||
|
||||
/**
|
||||
* Whether to use APR. If running on Java below 24, the default value is
|
||||
* 'WHEN_AVAILABLE'. On Java 24 or later, the default value is 'NEVER'.
|
||||
*/
|
||||
private UseApr useApr;
|
||||
|
||||
public DataSize getMaxHttpFormPostSize() {
|
||||
return this.maxHttpFormPostSize;
|
||||
}
|
||||
@@ -669,6 +676,18 @@ public class ServerProperties {
|
||||
this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize;
|
||||
}
|
||||
|
||||
public UseApr getUseApr() {
|
||||
if (this.useApr == null) {
|
||||
return JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.TWENTY_FOUR) ? UseApr.NEVER
|
||||
: UseApr.WHEN_AVAILABLE;
|
||||
}
|
||||
return this.useApr;
|
||||
}
|
||||
|
||||
public void setUseApr(UseApr useApr) {
|
||||
this.useApr = useApr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tomcat access log properties.
|
||||
*/
|
||||
@@ -1918,4 +1937,26 @@ public class ServerProperties {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* When to use APR.
|
||||
*/
|
||||
public enum UseApr {
|
||||
|
||||
/**
|
||||
* Always use APR and fail if it's not available.
|
||||
*/
|
||||
ALWAYS,
|
||||
|
||||
/**
|
||||
* Use APR if it is available.
|
||||
*/
|
||||
WHEN_AVAILABLE,
|
||||
|
||||
/**
|
||||
* Never user APR.
|
||||
*/
|
||||
NEVER
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,9 +16,14 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.web.reactive;
|
||||
|
||||
import org.apache.catalina.core.AprLifecycleListener;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties.UseApr;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link WebServerFactoryCustomizer} to apply {@link ServerProperties} to Tomcat reactive
|
||||
@@ -38,7 +43,27 @@ public class TomcatReactiveWebServerFactoryCustomizer
|
||||
|
||||
@Override
|
||||
public void customize(TomcatReactiveWebServerFactory factory) {
|
||||
factory.setDisableMBeanRegistry(!this.serverProperties.getTomcat().getMbeanregistry().isEnabled());
|
||||
Tomcat tomcatProperties = this.serverProperties.getTomcat();
|
||||
factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
|
||||
factory.setUseApr(getUseApr(tomcatProperties.getUseApr()));
|
||||
}
|
||||
|
||||
private boolean getUseApr(UseApr useApr) {
|
||||
return switch (useApr) {
|
||||
case ALWAYS -> {
|
||||
Assert.state(isAprAvailable(), "APR has been configured to 'ALWAYS', but it's not available");
|
||||
yield true;
|
||||
}
|
||||
case WHEN_AVAILABLE -> isAprAvailable();
|
||||
case NEVER -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isAprAvailable() {
|
||||
// At least one instance of AprLifecycleListener has to be created for
|
||||
// isAprAvailable() to work
|
||||
new AprLifecycleListener();
|
||||
return AprLifecycleListener.isAprAvailable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,11 +16,15 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.web.servlet;
|
||||
|
||||
import org.apache.catalina.core.AprLifecycleListener;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties.UseApr;
|
||||
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
@@ -56,6 +60,7 @@ public class TomcatServletWebServerFactoryCustomizer
|
||||
}
|
||||
customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());
|
||||
factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
|
||||
factory.setUseApr(getUseApr(tomcatProperties.getUseApr()));
|
||||
}
|
||||
|
||||
private void customizeRedirectContextRoot(ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) {
|
||||
@@ -67,4 +72,22 @@ public class TomcatServletWebServerFactoryCustomizer
|
||||
factory.addContextCustomizers((context) -> context.setUseRelativeRedirects(useRelativeRedirects));
|
||||
}
|
||||
|
||||
private boolean getUseApr(UseApr useApr) {
|
||||
return switch (useApr) {
|
||||
case ALWAYS -> {
|
||||
Assert.state(isAprAvailable(), "APR has been configured to 'ALWAYS', but it's not available");
|
||||
yield true;
|
||||
}
|
||||
case WHEN_AVAILABLE -> isAprAvailable();
|
||||
case NEVER -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isAprAvailable() {
|
||||
// At least one instance of AprLifecycleListener has to be created for
|
||||
// isAprAvailable() to work
|
||||
new AprLifecycleListener();
|
||||
return AprLifecycleListener.isAprAvailable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,9 +35,12 @@ import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import reactor.netty.http.HttpDecoderSpec;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Accesslog;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties.UseApr;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
|
||||
@@ -69,6 +72,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Chris Bono
|
||||
* @author Parviz Rozikov
|
||||
* @author Lasse Wulff
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
@DirtiesUrlFactories
|
||||
class ServerPropertiesTests {
|
||||
@@ -494,6 +498,18 @@ class ServerPropertiesTests {
|
||||
.isEqualTo(HttpDecoderSpec.DEFAULT_INITIAL_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledForJreRange(max = JRE.JAVA_23)
|
||||
void shouldDefaultAprToWhenAvailableUntilJava23() {
|
||||
assertThat(this.properties.getTomcat().getUseApr()).isEqualTo(UseApr.WHEN_AVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledForJreRange(min = JRE.JAVA_24)
|
||||
void shouldDefaultAprToNeverOnJava24AndLater() {
|
||||
assertThat(this.properties.getTomcat().getUseApr()).isEqualTo(UseApr.NEVER);
|
||||
}
|
||||
|
||||
private Connector getDefaultConnector() {
|
||||
return new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,6 +36,7 @@ import org.apache.catalina.connector.Connector;
|
||||
import org.apache.catalina.core.AprLifecycleListener;
|
||||
import org.apache.catalina.loader.WebappLoader;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.catalina.startup.Tomcat.FixContextListener;
|
||||
import org.apache.catalina.webresources.StandardRoot;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -83,8 +84,6 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
|
||||
|
||||
private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>();
|
||||
|
||||
private final List<LifecycleListener> serverLifecycleListeners = getDefaultServerLifecycleListeners();
|
||||
|
||||
private Set<TomcatContextCustomizer> tomcatContextCustomizers = new LinkedHashSet<>();
|
||||
|
||||
private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();
|
||||
@@ -101,6 +100,8 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
|
||||
|
||||
private boolean disableMBeanRegistry = true;
|
||||
|
||||
private boolean useApr;
|
||||
|
||||
/**
|
||||
* Create a new {@link TomcatReactiveWebServerFactory} instance.
|
||||
*/
|
||||
@@ -116,10 +117,12 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
|
||||
super(port);
|
||||
}
|
||||
|
||||
private static List<LifecycleListener> getDefaultServerLifecycleListeners() {
|
||||
AprLifecycleListener aprLifecycleListener = new AprLifecycleListener();
|
||||
return AprLifecycleListener.isAprAvailable() ? new ArrayList<>(Arrays.asList(aprLifecycleListener))
|
||||
: new ArrayList<>();
|
||||
private List<LifecycleListener> getDefaultServerLifecycleListeners() {
|
||||
ArrayList<LifecycleListener> lifecycleListeners = new ArrayList<>();
|
||||
if (this.useApr) {
|
||||
lifecycleListeners.add(new AprLifecycleListener());
|
||||
}
|
||||
return lifecycleListeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,7 +133,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
|
||||
Tomcat tomcat = new Tomcat();
|
||||
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
|
||||
tomcat.setBaseDir(baseDir.getAbsolutePath());
|
||||
for (LifecycleListener listener : this.serverLifecycleListeners) {
|
||||
for (LifecycleListener listener : getDefaultServerLifecycleListeners()) {
|
||||
tomcat.getServer().addLifecycleListener(listener);
|
||||
}
|
||||
Connector connector = new Connector(this.protocol);
|
||||
@@ -171,7 +174,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
|
||||
context.setResources(resourcesRoot);
|
||||
context.setPath("");
|
||||
context.setDocBase(docBase.getAbsolutePath());
|
||||
context.addLifecycleListener(new Tomcat.FixContextListener());
|
||||
context.addLifecycleListener(new FixContextListener());
|
||||
ClassLoader parentClassLoader = ClassUtils.getDefaultClassLoader();
|
||||
context.setParentClassLoader(parentClassLoader);
|
||||
skipAllTldScanning(context);
|
||||
@@ -476,4 +479,13 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
|
||||
this.disableMBeanRegistry = disableMBeanRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use APR.
|
||||
* @param useApr whether to use APR
|
||||
* @since 3.4.4
|
||||
*/
|
||||
public void setUseApr(boolean useApr) {
|
||||
this.useApr = useApr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -135,8 +135,6 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
|
||||
|
||||
private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>();
|
||||
|
||||
private final List<LifecycleListener> serverLifecycleListeners = getDefaultServerLifecycleListeners();
|
||||
|
||||
private Set<TomcatContextCustomizer> tomcatContextCustomizers = new LinkedHashSet<>();
|
||||
|
||||
private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();
|
||||
@@ -159,6 +157,8 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
|
||||
|
||||
private boolean disableMBeanRegistry = true;
|
||||
|
||||
private boolean useApr;
|
||||
|
||||
/**
|
||||
* Create a new {@link TomcatServletWebServerFactory} instance.
|
||||
*/
|
||||
@@ -184,13 +184,10 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
|
||||
super(contextPath, port);
|
||||
}
|
||||
|
||||
private static List<LifecycleListener> getDefaultServerLifecycleListeners() {
|
||||
private List<LifecycleListener> getDefaultServerLifecycleListeners() {
|
||||
ArrayList<LifecycleListener> lifecycleListeners = new ArrayList<>();
|
||||
if (!NativeDetector.inNativeImage()) {
|
||||
AprLifecycleListener aprLifecycleListener = new AprLifecycleListener();
|
||||
if (AprLifecycleListener.isAprAvailable()) {
|
||||
lifecycleListeners.add(aprLifecycleListener);
|
||||
}
|
||||
if (!NativeDetector.inNativeImage() && this.useApr) {
|
||||
lifecycleListeners.add(new AprLifecycleListener());
|
||||
}
|
||||
return lifecycleListeners;
|
||||
}
|
||||
@@ -203,7 +200,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
|
||||
Tomcat tomcat = new Tomcat();
|
||||
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
|
||||
tomcat.setBaseDir(baseDir.getAbsolutePath());
|
||||
for (LifecycleListener listener : this.serverLifecycleListeners) {
|
||||
for (LifecycleListener listener : getDefaultServerLifecycleListeners()) {
|
||||
tomcat.getServer().addLifecycleListener(listener);
|
||||
}
|
||||
Connector connector = new Connector(this.protocol);
|
||||
@@ -784,6 +781,15 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
|
||||
this.disableMBeanRegistry = disableMBeanRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use APR.
|
||||
* @param useApr whether to use APR
|
||||
* @since 3.4.4
|
||||
*/
|
||||
public void setUseApr(boolean useApr) {
|
||||
this.useApr = useApr;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link LifecycleListener} to disable persistence in the {@link StandardManager}. A
|
||||
* {@link LifecycleListener} is used so not to interfere with Tomcat's default manager
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -95,12 +95,20 @@ class TomcatReactiveWebServerFactoryTests extends AbstractReactiveWebServerFacto
|
||||
@Test
|
||||
void defaultTomcatListeners() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
if (AprLifecycleListener.isAprAvailable()) {
|
||||
assertThat(factory.getContextLifecycleListeners()).singleElement().isInstanceOf(AprLifecycleListener.class);
|
||||
}
|
||||
else {
|
||||
assertThat(factory.getContextLifecycleListeners()).isEmpty();
|
||||
}
|
||||
assertThat(factory.getContextLifecycleListeners()).isEmpty();
|
||||
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class));
|
||||
this.webServer = tomcatWebServer;
|
||||
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aprShouldBeOptIn() {
|
||||
TomcatReactiveWebServerFactory factory = getFactory();
|
||||
factory.setUseApr(true);
|
||||
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class));
|
||||
this.webServer = tomcatWebServer;
|
||||
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).singleElement()
|
||||
.isInstanceOf(AprLifecycleListener.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -150,12 +150,20 @@ class TomcatServletWebServerFactoryTests extends AbstractServletWebServerFactory
|
||||
@Test
|
||||
void defaultTomcatListeners() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
if (AprLifecycleListener.isAprAvailable()) {
|
||||
assertThat(factory.getContextLifecycleListeners()).singleElement().isInstanceOf(AprLifecycleListener.class);
|
||||
}
|
||||
else {
|
||||
assertThat(factory.getContextLifecycleListeners()).isEmpty();
|
||||
}
|
||||
assertThat(factory.getContextLifecycleListeners()).isEmpty();
|
||||
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer();
|
||||
this.webServer = tomcatWebServer;
|
||||
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aprShouldBeOptIn() {
|
||||
TomcatServletWebServerFactory factory = getFactory();
|
||||
factory.setUseApr(true);
|
||||
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer();
|
||||
this.webServer = tomcatWebServer;
|
||||
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).singleElement()
|
||||
.isInstanceOf(AprLifecycleListener.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -67,15 +67,12 @@ class LoaderIntegrationTests {
|
||||
.withLogConsumer(this.output)
|
||||
.withCopyFileToContainer(MountableFile.forHostPath(findApplication().toPath()), "/app.jar")
|
||||
.withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofMinutes(5)))
|
||||
.withCommand(command(javaRuntime));
|
||||
.withCommand(command());
|
||||
}
|
||||
|
||||
private String[] command(JavaRuntime javaRuntime) {
|
||||
private String[] command() {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("java");
|
||||
if (javaRuntime.version == JavaVersion.TWENTY_FOUR) {
|
||||
command.add("--enable-native-access=ALL-UNNAMED");
|
||||
}
|
||||
command.add("-jar");
|
||||
command.add("app.jar");
|
||||
return command.toArray(new String[0]);
|
||||
|
||||
@@ -90,15 +90,12 @@ class LoaderIntegrationTests {
|
||||
.withLogConsumer(this.output)
|
||||
.withCopyFileToContainer(findApplication(name, classifier), "/app.jar")
|
||||
.withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofMinutes(5)))
|
||||
.withCommand(command(javaRuntime));
|
||||
.withCommand(command());
|
||||
}
|
||||
|
||||
private String[] command(JavaRuntime javaRuntime) {
|
||||
private String[] command() {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("java");
|
||||
if (javaRuntime.version == JavaVersion.TWENTY_FOUR) {
|
||||
command.add("--enable-native-access=ALL-UNNAMED");
|
||||
}
|
||||
command.add("-jar");
|
||||
command.add("app.jar");
|
||||
return command.toArray(new String[0]);
|
||||
|
||||
Reference in New Issue
Block a user