Skip to content

Commit a53aba6

Browse files
committed
Merge branch '3.4.x'
This sets the default value of 'server.tomcat.use-apr' to 'NEVER'. Closes gh-44705
2 parents 19c3860 + 7fcf34e commit a53aba6

File tree

10 files changed

+160
-42
lines changed

10 files changed

+160
-42
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,11 @@ public static class Tomcat {
519519
*/
520520
private int maxParameterCount = 10000;
521521

522+
/**
523+
* Whether to use APR.
524+
*/
525+
private UseApr useApr = UseApr.NEVER;
526+
522527
public Accesslog getAccesslog() {
523528
return this.accesslog;
524529
}
@@ -683,6 +688,14 @@ public void setMaxParameterCount(int maxParameterCount) {
683688
this.maxParameterCount = maxParameterCount;
684689
}
685690

691+
public UseApr getUseApr() {
692+
return this.useApr;
693+
}
694+
695+
public void setUseApr(UseApr useApr) {
696+
this.useApr = useApr;
697+
}
698+
686699
/**
687700
* Tomcat access log properties.
688701
*/
@@ -1932,4 +1945,26 @@ public enum ForwardHeadersStrategy {
19321945

19331946
}
19341947

1948+
/**
1949+
* When to use APR.
1950+
*/
1951+
public enum UseApr {
1952+
1953+
/**
1954+
* Always use APR and fail if it's not available.
1955+
*/
1956+
ALWAYS,
1957+
1958+
/**
1959+
* Use APR if it is available.
1960+
*/
1961+
WHEN_AVAILABLE,
1962+
1963+
/**
1964+
* Never user APR.
1965+
*/
1966+
NEVER
1967+
1968+
}
1969+
19351970
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/TomcatReactiveWebServerFactoryCustomizer.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,9 +16,14 @@
1616

1717
package org.springframework.boot.autoconfigure.web.reactive;
1818

19+
import org.apache.catalina.core.AprLifecycleListener;
20+
1921
import org.springframework.boot.autoconfigure.web.ServerProperties;
22+
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat;
23+
import org.springframework.boot.autoconfigure.web.ServerProperties.UseApr;
2024
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
2125
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
26+
import org.springframework.util.Assert;
2227

2328
/**
2429
* {@link WebServerFactoryCustomizer} to apply {@link ServerProperties} to Tomcat reactive
@@ -38,7 +43,27 @@ public TomcatReactiveWebServerFactoryCustomizer(ServerProperties serverPropertie
3843

3944
@Override
4045
public void customize(TomcatReactiveWebServerFactory factory) {
41-
factory.setDisableMBeanRegistry(!this.serverProperties.getTomcat().getMbeanregistry().isEnabled());
46+
Tomcat tomcatProperties = this.serverProperties.getTomcat();
47+
factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
48+
factory.setUseApr(getUseApr(tomcatProperties.getUseApr()));
49+
}
50+
51+
private boolean getUseApr(UseApr useApr) {
52+
return switch (useApr) {
53+
case ALWAYS -> {
54+
Assert.state(isAprAvailable(), "APR has been configured to 'ALWAYS', but it's not available");
55+
yield true;
56+
}
57+
case WHEN_AVAILABLE -> isAprAvailable();
58+
case NEVER -> false;
59+
};
60+
}
61+
62+
private boolean isAprAvailable() {
63+
// At least one instance of AprLifecycleListener has to be created for
64+
// isAprAvailable() to work
65+
new AprLifecycleListener();
66+
return AprLifecycleListener.isAprAvailable();
4267
}
4368

4469
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/TomcatServletWebServerFactoryCustomizer.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,11 +16,15 @@
1616

1717
package org.springframework.boot.autoconfigure.web.servlet;
1818

19+
import org.apache.catalina.core.AprLifecycleListener;
20+
1921
import org.springframework.boot.autoconfigure.web.ServerProperties;
22+
import org.springframework.boot.autoconfigure.web.ServerProperties.UseApr;
2023
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
2124
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
2225
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
2326
import org.springframework.core.Ordered;
27+
import org.springframework.util.Assert;
2428
import org.springframework.util.ObjectUtils;
2529

2630
/**
@@ -56,6 +60,7 @@ public void customize(TomcatServletWebServerFactory factory) {
5660
}
5761
customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());
5862
factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
63+
factory.setUseApr(getUseApr(tomcatProperties.getUseApr()));
5964
}
6065

6166
private void customizeRedirectContextRoot(ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) {
@@ -67,4 +72,22 @@ private void customizeUseRelativeRedirects(ConfigurableTomcatWebServerFactory fa
6772
factory.addContextCustomizers((context) -> context.setUseRelativeRedirects(useRelativeRedirects));
6873
}
6974

75+
private boolean getUseApr(UseApr useApr) {
76+
return switch (useApr) {
77+
case ALWAYS -> {
78+
Assert.state(isAprAvailable(), "APR has been configured to 'ALWAYS', but it's not available");
79+
yield true;
80+
}
81+
case WHEN_AVAILABLE -> isAprAvailable();
82+
case NEVER -> false;
83+
};
84+
}
85+
86+
private boolean isAprAvailable() {
87+
// At least one instance of AprLifecycleListener has to be created for
88+
// isAprAvailable() to work
89+
new AprLifecycleListener();
90+
return AprLifecycleListener.isAprAvailable();
91+
}
92+
7093
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import reactor.netty.http.HttpDecoderSpec;
3939

4040
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Accesslog;
41+
import org.springframework.boot.autoconfigure.web.ServerProperties.UseApr;
4142
import org.springframework.boot.context.properties.bind.Bindable;
4243
import org.springframework.boot.context.properties.bind.Binder;
4344
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
@@ -69,6 +70,7 @@
6970
* @author Chris Bono
7071
* @author Parviz Rozikov
7172
* @author Lasse Wulff
73+
* @author Moritz Halbritter
7274
*/
7375
@DirtiesUrlFactories
7476
class ServerPropertiesTests {
@@ -506,6 +508,11 @@ void nettyInitialBufferSizeMatchesHttpDecoderSpecDefault() {
506508
.isEqualTo(HttpDecoderSpec.DEFAULT_INITIAL_BUFFER_SIZE);
507509
}
508510

511+
@Test
512+
void shouldDefaultAprToNever() {
513+
assertThat(this.properties.getTomcat().getUseApr()).isEqualTo(UseApr.NEVER);
514+
}
515+
509516
private Connector getDefaultConnector() {
510517
return new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
511518
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.apache.catalina.core.AprLifecycleListener;
3737
import org.apache.catalina.loader.WebappLoader;
3838
import org.apache.catalina.startup.Tomcat;
39+
import org.apache.catalina.startup.Tomcat.FixContextListener;
3940
import org.apache.catalina.webresources.StandardRoot;
4041
import org.apache.commons.logging.Log;
4142
import org.apache.commons.logging.LogFactory;
@@ -83,8 +84,6 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
8384

8485
private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>();
8586

86-
private final List<LifecycleListener> serverLifecycleListeners = getDefaultServerLifecycleListeners();
87-
8887
private Set<TomcatContextCustomizer> tomcatContextCustomizers = new LinkedHashSet<>();
8988

9089
private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();
@@ -101,6 +100,8 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
101100

102101
private boolean disableMBeanRegistry = true;
103102

103+
private boolean useApr;
104+
104105
/**
105106
* Create a new {@link TomcatReactiveWebServerFactory} instance.
106107
*/
@@ -116,10 +117,12 @@ public TomcatReactiveWebServerFactory(int port) {
116117
super(port);
117118
}
118119

119-
private static List<LifecycleListener> getDefaultServerLifecycleListeners() {
120-
AprLifecycleListener aprLifecycleListener = new AprLifecycleListener();
121-
return AprLifecycleListener.isAprAvailable() ? new ArrayList<>(Arrays.asList(aprLifecycleListener))
122-
: new ArrayList<>();
120+
private List<LifecycleListener> getDefaultServerLifecycleListeners() {
121+
ArrayList<LifecycleListener> lifecycleListeners = new ArrayList<>();
122+
if (this.useApr) {
123+
lifecycleListeners.add(new AprLifecycleListener());
124+
}
125+
return lifecycleListeners;
123126
}
124127

125128
@Override
@@ -130,7 +133,7 @@ public WebServer getWebServer(HttpHandler httpHandler) {
130133
Tomcat tomcat = new Tomcat();
131134
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
132135
tomcat.setBaseDir(baseDir.getAbsolutePath());
133-
for (LifecycleListener listener : this.serverLifecycleListeners) {
136+
for (LifecycleListener listener : getDefaultServerLifecycleListeners()) {
134137
tomcat.getServer().addLifecycleListener(listener);
135138
}
136139
Connector connector = new Connector(this.protocol);
@@ -171,7 +174,7 @@ protected void prepareContext(Host host, TomcatHttpHandlerAdapter servlet) {
171174
context.setResources(resourcesRoot);
172175
context.setPath("");
173176
context.setDocBase(docBase.getAbsolutePath());
174-
context.addLifecycleListener(new Tomcat.FixContextListener());
177+
context.addLifecycleListener(new FixContextListener());
175178
ClassLoader parentClassLoader = ClassUtils.getDefaultClassLoader();
176179
context.setParentClassLoader(parentClassLoader);
177180
skipAllTldScanning(context);
@@ -476,4 +479,13 @@ public void setDisableMBeanRegistry(boolean disableMBeanRegistry) {
476479
this.disableMBeanRegistry = disableMBeanRegistry;
477480
}
478481

482+
/**
483+
* Whether to use APR.
484+
* @param useApr whether to use APR
485+
* @since 3.4.4
486+
*/
487+
public void setUseApr(boolean useApr) {
488+
this.useApr = useApr;
489+
}
490+
479491
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,6 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
135135

136136
private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>();
137137

138-
private final List<LifecycleListener> serverLifecycleListeners = getDefaultServerLifecycleListeners();
139-
140138
private Set<TomcatContextCustomizer> tomcatContextCustomizers = new LinkedHashSet<>();
141139

142140
private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();
@@ -159,6 +157,8 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
159157

160158
private boolean disableMBeanRegistry = true;
161159

160+
private boolean useApr;
161+
162162
/**
163163
* Create a new {@link TomcatServletWebServerFactory} instance.
164164
*/
@@ -184,13 +184,10 @@ public TomcatServletWebServerFactory(String contextPath, int port) {
184184
super(contextPath, port);
185185
}
186186

187-
private static List<LifecycleListener> getDefaultServerLifecycleListeners() {
187+
private List<LifecycleListener> getDefaultServerLifecycleListeners() {
188188
ArrayList<LifecycleListener> lifecycleListeners = new ArrayList<>();
189-
if (!NativeDetector.inNativeImage()) {
190-
AprLifecycleListener aprLifecycleListener = new AprLifecycleListener();
191-
if (AprLifecycleListener.isAprAvailable()) {
192-
lifecycleListeners.add(aprLifecycleListener);
193-
}
189+
if (!NativeDetector.inNativeImage() && this.useApr) {
190+
lifecycleListeners.add(new AprLifecycleListener());
194191
}
195192
return lifecycleListeners;
196193
}
@@ -203,7 +200,7 @@ public WebServer getWebServer(ServletContextInitializer... initializers) {
203200
Tomcat tomcat = new Tomcat();
204201
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
205202
tomcat.setBaseDir(baseDir.getAbsolutePath());
206-
for (LifecycleListener listener : this.serverLifecycleListeners) {
203+
for (LifecycleListener listener : getDefaultServerLifecycleListeners()) {
207204
tomcat.getServer().addLifecycleListener(listener);
208205
}
209206
Connector connector = new Connector(this.protocol);
@@ -784,6 +781,15 @@ public void setDisableMBeanRegistry(boolean disableMBeanRegistry) {
784781
this.disableMBeanRegistry = disableMBeanRegistry;
785782
}
786783

784+
/**
785+
* Whether to use APR.
786+
* @param useApr whether to use APR
787+
* @since 3.4.4
788+
*/
789+
public void setUseApr(boolean useApr) {
790+
this.useApr = useApr;
791+
}
792+
787793
/**
788794
* {@link LifecycleListener} to disable persistence in the {@link StandardManager}. A
789795
* {@link LifecycleListener} is used so not to interfere with Tomcat's default manager

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,20 @@ void contextIsAddedToHostBeforeCustomizersAreCalled() {
9595
@Test
9696
void defaultTomcatListeners() {
9797
TomcatReactiveWebServerFactory factory = getFactory();
98-
if (AprLifecycleListener.isAprAvailable()) {
99-
assertThat(factory.getContextLifecycleListeners()).singleElement().isInstanceOf(AprLifecycleListener.class);
100-
}
101-
else {
102-
assertThat(factory.getContextLifecycleListeners()).isEmpty();
103-
}
98+
assertThat(factory.getContextLifecycleListeners()).isEmpty();
99+
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class));
100+
this.webServer = tomcatWebServer;
101+
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).isEmpty();
102+
}
103+
104+
@Test
105+
void aprShouldBeOptIn() {
106+
TomcatReactiveWebServerFactory factory = getFactory();
107+
factory.setUseApr(true);
108+
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class));
109+
this.webServer = tomcatWebServer;
110+
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).singleElement()
111+
.isInstanceOf(AprLifecycleListener.class);
104112
}
105113

106114
@Test

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,20 @@ void tomcatEngineNames() {
150150
@Test
151151
void defaultTomcatListeners() {
152152
TomcatServletWebServerFactory factory = getFactory();
153-
if (AprLifecycleListener.isAprAvailable()) {
154-
assertThat(factory.getContextLifecycleListeners()).singleElement().isInstanceOf(AprLifecycleListener.class);
155-
}
156-
else {
157-
assertThat(factory.getContextLifecycleListeners()).isEmpty();
158-
}
153+
assertThat(factory.getContextLifecycleListeners()).isEmpty();
154+
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer();
155+
this.webServer = tomcatWebServer;
156+
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).isEmpty();
157+
}
158+
159+
@Test
160+
void aprShouldBeOptIn() {
161+
TomcatServletWebServerFactory factory = getFactory();
162+
factory.setUseApr(true);
163+
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer();
164+
this.webServer = tomcatWebServer;
165+
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).singleElement()
166+
.isInstanceOf(AprLifecycleListener.class);
159167
}
160168

161169
@Test

spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-classic-tests/src/dockerTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,12 @@ private GenericContainer<?> createContainer(JavaRuntime javaRuntime) {
6767
.withLogConsumer(this.output)
6868
.withCopyFileToContainer(MountableFile.forHostPath(findApplication().toPath()), "/app.jar")
6969
.withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofMinutes(5)))
70-
.withCommand(command(javaRuntime));
70+
.withCommand(command());
7171
}
7272

73-
private String[] command(JavaRuntime javaRuntime) {
73+
private String[] command() {
7474
List<String> command = new ArrayList<>();
7575
command.add("java");
76-
if (javaRuntime.version == JavaVersion.TWENTY_FOUR) {
77-
command.add("--enable-native-access=ALL-UNNAMED");
78-
}
7976
command.add("-jar");
8077
command.add("app.jar");
8178
return command.toArray(new String[0]);

0 commit comments

Comments
 (0)