Move Servlet high-level infrastructure to spring-boot-servlet

This commit is contained in:
Stéphane Nicoll
2025-06-03 09:01:03 +02:00
committed by Phillip Webb
parent 9ee79c1bbc
commit b379bc0deb
53 changed files with 184 additions and 91 deletions

View File

@@ -0,0 +1,25 @@
plugins {
id "java-library"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
}
description = "Spring Boot Servlet"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api("org.springframework:spring-web")
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
optional(project(":spring-boot-project:spring-boot-web-server"))
optional("jakarta.servlet:jakarta.servlet-api")
optional("org.springframework.security:spring-security-config")
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testRuntimeOnly("ch.qos.logback:logback-classic")
}

View File

@@ -0,0 +1,97 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet;
import jakarta.servlet.MultipartConfigElement;
import org.springframework.util.unit.DataSize;
/**
* Factory that can be used to create a {@link MultipartConfigElement}.
*
* @author Phillip Webb
* @since 4.0.0
*/
public class MultipartConfigFactory {
private String location;
private DataSize maxFileSize;
private DataSize maxRequestSize;
private DataSize fileSizeThreshold;
/**
* Sets the directory location where files will be stored.
* @param location the location
*/
public void setLocation(String location) {
this.location = location;
}
/**
* Sets the maximum {@link DataSize size} allowed for uploaded files.
* @param maxFileSize the maximum file size
*/
public void setMaxFileSize(DataSize maxFileSize) {
this.maxFileSize = maxFileSize;
}
/**
* Sets the maximum {@link DataSize} allowed for multipart/form-data requests.
* @param maxRequestSize the maximum request size
*/
public void setMaxRequestSize(DataSize maxRequestSize) {
this.maxRequestSize = maxRequestSize;
}
/**
* Sets the {@link DataSize size} threshold after which files will be written to disk.
* @param fileSizeThreshold the file size threshold
*/
public void setFileSizeThreshold(DataSize fileSizeThreshold) {
this.fileSizeThreshold = fileSizeThreshold;
}
/**
* Create a new {@link MultipartConfigElement} instance.
* @return the multipart config element
*/
public MultipartConfigElement createMultipartConfig() {
long maxFileSizeBytes = convertToBytes(this.maxFileSize, -1);
long maxRequestSizeBytes = convertToBytes(this.maxRequestSize, -1);
long fileSizeThresholdBytes = convertToBytes(this.fileSizeThreshold, 0);
return new MultipartConfigElement(this.location, maxFileSizeBytes, maxRequestSizeBytes,
(int) fileSizeThresholdBytes);
}
/**
* Return the amount of bytes from the specified {@link DataSize size}. If the size is
* {@code null} or negative, returns {@code defaultValue}.
* @param size the data size to handle
* @param defaultValue the default value if the size is {@code null} or negative
* @return the amount of bytes to use
*/
private long convertToBytes(DataSize size, int defaultValue) {
if (size != null && !size.isNegative()) {
return size.toBytes();
}
return defaultValue;
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.autoconfigure;
/**
* Provides information about the management servlet context for MVC controllers to use.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 4.0.0
*/
@FunctionalInterface
public interface ManagementServletContext {
/**
* Return the servlet path of the management server.
* @return the servlet path
*/
String getServletPath();
}

View File

@@ -0,0 +1,79 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.autoconfigure;
import jakarta.servlet.Filter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
/**
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for Servlet web
* endpoint infrastructure when a separate management context with a web server running on
* a different port is required.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Phillip Webb
* @author Moritz Halbritter
*/
@ManagementContextConfiguration(value = ManagementContextType.CHILD, proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class ServletManagementChildContextConfiguration {
@Bean
ServletManagementWebServerFactoryCustomizer servletManagementWebServerFactoryCustomizer(
ListableBeanFactory beanFactory) {
return new ServletManagementWebServerFactoryCustomizer(beanFactory);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ EnableWebSecurity.class, Filter.class })
@ConditionalOnBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN, search = SearchStrategy.ANCESTORS)
static class ServletManagementContextSecurityConfiguration {
@Bean
Filter springSecurityFilterChain(HierarchicalBeanFactory beanFactory) {
BeanFactory parent = beanFactory.getParentBeanFactory();
return parent.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN, Filter.class);
}
@Bean
@ConditionalOnBean(name = "securityFilterChainRegistration", search = SearchStrategy.ANCESTORS)
DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(HierarchicalBeanFactory beanFactory) {
return beanFactory.getParentBeanFactory()
.getBean("securityFilterChainRegistration", DelegatingFilterProxyRegistrationBean.class);
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.autoconfigure;
import jakarta.servlet.Servlet;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.filter.ApplicationContextHeaderFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Servlet-specific management
* context concerns.
*
* @author Phillip Webb
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass({ Servlet.class, WebEndpointProperties.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(WebEndpointProperties.class)
public class ServletManagementContextAutoConfiguration {
@Bean
public ManagementServletContext managementServletContext(WebEndpointProperties properties) {
return properties::getBasePath;
}
// Put Servlets and Filters in their own nested class so they don't force early
// instantiation of ManagementServerProperties.
@Configuration(proxyBeanMethods = false)
@ConditionalOnBooleanProperty("management.server.add-application-context-header")
protected static class ApplicationContextFilterConfiguration {
@Bean
public ApplicationContextHeaderFilter applicationContextIdFilter(ApplicationContext context) {
return new ApplicationContextHeaderFilter(context);
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.autoconfigure;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementWebServerFactoryCustomizer;
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
import org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory;
import org.springframework.util.StringUtils;
/**
* {@link ManagementWebServerFactoryCustomizer} for a servlet web server.
*
* @author Andy Wilkinson
*/
class ServletManagementWebServerFactoryCustomizer
extends ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
ServletManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
protected void customize(ConfigurableServletWebServerFactory webServerFactory,
ManagementServerProperties managementServerProperties, ServerProperties serverProperties) {
super.customize(webServerFactory, managementServerProperties, serverProperties);
webServerFactory.setContextPath(getContextPath(managementServerProperties));
}
private String getContextPath(ManagementServerProperties managementServerProperties) {
String basePath = managementServerProperties.getBasePath();
return StringUtils.hasText(basePath) ? basePath : "";
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.autoconfigure.endpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.context.annotation.Bean;
/**
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for servlet
* endpoints.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 4.0.0
*/
@ManagementContextConfiguration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class ServletEndpointManagementContextConfiguration {
@Bean
@SuppressWarnings("removal")
public IncludeExcludeEndpointFilter<org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter(
WebEndpointProperties properties) {
WebEndpointProperties.Exposure exposure = properties.getExposure();
return new IncludeExcludeEndpointFilter<>(
org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint.class, exposure.getInclude(),
exposure.getExclude());
}
}

View File

@@ -0,0 +1,20 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for the Actuator's servlet web endpoints.
*/
package org.springframework.boot.servlet.actuate.autoconfigure.endpoint;

View File

@@ -0,0 +1,20 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Actuator Servlet support.
*/
package org.springframework.boot.servlet.actuate.autoconfigure;

View File

@@ -0,0 +1,56 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.mappings;
import java.util.Collection;
import jakarta.servlet.FilterRegistration;
/**
* A {@link RegistrationMappingDescription} derived from a {@link FilterRegistration}.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
public class FilterRegistrationMappingDescription extends RegistrationMappingDescription<FilterRegistration> {
/**
* Creates a new {@code FilterRegistrationMappingDescription} derived from the given
* {@code filterRegistration}.
* @param filterRegistration the filter registration
*/
public FilterRegistrationMappingDescription(FilterRegistration filterRegistration) {
super(filterRegistration);
}
/**
* Returns the servlet name mappings for the registered filter.
* @return the mappings
*/
public Collection<String> getServletNameMappings() {
return getRegistration().getServletNameMappings();
}
/**
* Returns the URL pattern mappings for the registered filter.
* @return the mappings
*/
public Collection<String> getUrlPatternMappings() {
return getRegistration().getUrlPatternMappings();
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.mappings;
import java.util.Collections;
import java.util.List;
import jakarta.servlet.Filter;
import jakarta.servlet.ServletContext;
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.actuate.web.mappings.MappingDescriptionProvider;
import org.springframework.boot.servlet.actuate.mappings.FiltersMappingDescriptionProvider.FiltersMappingDescriptionProviderRuntimeHints;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.web.context.WebApplicationContext;
/**
* A {@link MappingDescriptionProvider} that describes that mappings of any {@link Filter
* Filters} registered with a {@link ServletContext}.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
@ImportRuntimeHints(FiltersMappingDescriptionProviderRuntimeHints.class)
public class FiltersMappingDescriptionProvider implements MappingDescriptionProvider {
@Override
public List<FilterRegistrationMappingDescription> describeMappings(ApplicationContext context) {
if (context instanceof WebApplicationContext webApplicationContext) {
return webApplicationContext.getServletContext()
.getFilterRegistrations()
.values()
.stream()
.map(FilterRegistrationMappingDescription::new)
.toList();
}
return Collections.emptyList();
}
@Override
public String getMappingName() {
return "servletFilters";
}
static class FiltersMappingDescriptionProviderRuntimeHints implements RuntimeHintsRegistrar {
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
this.bindingRegistrar.registerReflectionHints(hints.reflection(),
FilterRegistrationMappingDescription.class);
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.mappings;
import jakarta.servlet.Registration;
/**
* A mapping description derived from a {@link Registration}.
*
* @param <T> type of the registration
* @author Andy Wilkinson
* @since 4.0.0
*/
public class RegistrationMappingDescription<T extends Registration> {
private final T registration;
/**
* Creates a new {@link RegistrationMappingDescription} derived from the given
* {@code registration} and with the given {@code predicate}.
* @param registration the registration
*/
public RegistrationMappingDescription(T registration) {
this.registration = registration;
}
/**
* Returns the name of the registered Filter or Servlet.
* @return the name
*/
public String getName() {
return this.registration.getName();
}
/**
* Returns the class name of the registered Filter or Servlet.
* @return the class name
*/
public String getClassName() {
return this.registration.getClassName();
}
/**
* Returns the registration that is being described.
* @return the registration
*/
protected final T getRegistration() {
return this.registration;
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.mappings;
import java.util.Collection;
import jakarta.servlet.ServletRegistration;
/**
* A mapping description derived from a {@link ServletRegistration}.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
public class ServletRegistrationMappingDescription extends RegistrationMappingDescription<ServletRegistration> {
/**
* Creates a new {@code ServletRegistrationMappingDescription} derived from the given
* {@code servletRegistration}.
* @param servletRegistration the servlet registration
*/
public ServletRegistrationMappingDescription(ServletRegistration servletRegistration) {
super(servletRegistration);
}
/**
* Returns the mappings for the registered servlet.
* @return the mappings
*/
public Collection<String> getMappings() {
return getRegistration().getMappings();
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.mappings;
import java.util.Collections;
import java.util.List;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContext;
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.actuate.web.mappings.MappingDescriptionProvider;
import org.springframework.boot.servlet.actuate.mappings.ServletsMappingDescriptionProvider.ServletsMappingDescriptionProviderRuntimeHints;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.web.context.WebApplicationContext;
/**
* A {@link MappingDescriptionProvider} that describes that mappings of any {@link Servlet
* Servlets} registered with a {@link ServletContext}.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
@ImportRuntimeHints(ServletsMappingDescriptionProviderRuntimeHints.class)
public class ServletsMappingDescriptionProvider implements MappingDescriptionProvider {
@Override
public List<ServletRegistrationMappingDescription> describeMappings(ApplicationContext context) {
if (context instanceof WebApplicationContext webApplicationContext) {
return webApplicationContext.getServletContext()
.getServletRegistrations()
.values()
.stream()
.map(ServletRegistrationMappingDescription::new)
.toList();
}
return Collections.emptyList();
}
@Override
public String getMappingName() {
return "servlets";
}
static class ServletsMappingDescriptionProviderRuntimeHints implements RuntimeHintsRegistrar {
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
this.bindingRegistrar.registerReflectionHints(hints.reflection(),
ServletRegistrationMappingDescription.class);
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Actuator servlet request mappings support.
*/
package org.springframework.boot.servlet.actuate.mappings;

View File

@@ -0,0 +1,83 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.autoconfigure;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.Servlet;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
/**
* {@link EnableAutoConfiguration Auto-configuration} for multipart uploads. Adds a
* {@link StandardServletMultipartResolver} if none is present, and adds a
* {@link jakarta.servlet.MultipartConfigElement multipartConfigElement} if none is
* otherwise defined.
* <p>
* The {@link jakarta.servlet.MultipartConfigElement} is a Servlet API that's used to
* configure how the server handles file uploads.
*
* @author Greg Turnquist
* @author Josh Long
* @author Toshiaki Maki
* @author Yanming Zhou
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnBooleanProperty(name = "spring.servlet.multipart.enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {
/**
* Well-known name for the MultipartResolver object in the bean factory for this
* namespace.
*/
private static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
private final MultipartProperties multipartProperties;
public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
this.multipartProperties = multipartProperties;
}
@Bean
@ConditionalOnMissingBean
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}
@Bean(name = MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
multipartResolver.setStrictServletCompliance(this.multipartProperties.isStrictServletCompliance());
return multipartResolver;
}
}

View File

@@ -0,0 +1,159 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.autoconfigure;
import jakarta.servlet.MultipartConfigElement;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.servlet.MultipartConfigFactory;
import org.springframework.util.unit.DataSize;
/**
* Properties to be used in configuring a {@link MultipartConfigElement}.
* <ul>
* <li>{@link #getLocation() location} specifies the directory where uploaded files will
* be stored. When not specified, a temporary directory will be used.</li>
* <li>{@link #getMaxFileSize() max-file-size} specifies the maximum size permitted for
* uploaded files. The default is 1MB</li>
* <li>{@link #getMaxRequestSize() max-request-size} specifies the maximum size allowed
* for {@literal multipart/form-data} requests. The default is 10MB.</li>
* <li>{@link #getFileSizeThreshold() file-size-threshold} specifies the size threshold
* after which files will be written to disk. The default is 0.</li>
* </ul>
* <p>
* These properties are ultimately passed to {@link MultipartConfigFactory} which means
* you may specify numeric values using {@literal long} values or using more readable
* {@link DataSize} variants.
*
* @author Josh Long
* @author Toshiaki Maki
* @author Stephane Nicoll
* @author Yanming Zhou
* @since 4.0.0
*/
@ConfigurationProperties(prefix = "spring.servlet.multipart", ignoreUnknownFields = false)
public class MultipartProperties {
/**
* Whether to enable support of multipart uploads.
*/
private boolean enabled = true;
/**
* Intermediate location of uploaded files.
*/
private String location;
/**
* Max file size.
*/
private DataSize maxFileSize = DataSize.ofMegabytes(1);
/**
* Max request size.
*/
private DataSize maxRequestSize = DataSize.ofMegabytes(10);
/**
* Threshold after which files are written to disk.
*/
private DataSize fileSizeThreshold = DataSize.ofBytes(0);
/**
* Whether to resolve the multipart request lazily at the time of file or parameter
* access.
*/
private boolean resolveLazily = false;
/**
* Whether to resolve the multipart request strictly complying with the Servlet
* specification, only to be used for "multipart/form-data" requests.
*/
private boolean strictServletCompliance = false;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getLocation() {
return this.location;
}
public void setLocation(String location) {
this.location = location;
}
public DataSize getMaxFileSize() {
return this.maxFileSize;
}
public void setMaxFileSize(DataSize maxFileSize) {
this.maxFileSize = maxFileSize;
}
public DataSize getMaxRequestSize() {
return this.maxRequestSize;
}
public void setMaxRequestSize(DataSize maxRequestSize) {
this.maxRequestSize = maxRequestSize;
}
public DataSize getFileSizeThreshold() {
return this.fileSizeThreshold;
}
public void setFileSizeThreshold(DataSize fileSizeThreshold) {
this.fileSizeThreshold = fileSizeThreshold;
}
public boolean isResolveLazily() {
return this.resolveLazily;
}
public void setResolveLazily(boolean resolveLazily) {
this.resolveLazily = resolveLazily;
}
public boolean isStrictServletCompliance() {
return this.strictServletCompliance;
}
public void setStrictServletCompliance(boolean strictServletCompliance) {
this.strictServletCompliance = strictServletCompliance;
}
/**
* Create a new {@link MultipartConfigElement} using the properties.
* @return a new {@link MultipartConfigElement} configured using there properties
*/
public MultipartConfigElement createMultipartConfig() {
MultipartConfigFactory factory = new MultipartConfigFactory();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.fileSizeThreshold).to(factory::setFileSizeThreshold);
map.from(this.location).whenHasText().to(factory::setLocation);
map.from(this.maxRequestSize).to(factory::setMaxRequestSize);
map.from(this.maxFileSize).to(factory::setMaxFileSize);
return factory.createMultipartConfig();
}
}

View File

@@ -0,0 +1,21 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for application support of the {@code jakarta.servlet}
* specification.
*/
package org.springframework.boot.servlet.autoconfigure;

View File

@@ -0,0 +1,20 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Application support for the {@code jakarta.servlet} specification.
*/
package org.springframework.boot.servlet;

View File

@@ -0,0 +1,2 @@
org.springframework.boot.servlet.actuate.autoconfigure.endpoint.ServletEndpointManagementContextConfiguration
org.springframework.boot.servlet.actuate.autoconfigure.ServletManagementChildContextConfiguration

View File

@@ -0,0 +1,2 @@
org.springframework.boot.servlet.actuate.autoconfigure.ServletManagementContextAutoConfiguration
org.springframework.boot.servlet.autoconfigure.MultipartAutoConfiguration

View File

@@ -0,0 +1,68 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet;
import jakarta.servlet.MultipartConfigElement;
import org.junit.jupiter.api.Test;
import org.springframework.util.unit.DataSize;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MultipartConfigFactory}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
class MultipartConfigFactoryTests {
@Test
void sensibleDefaults() {
MultipartConfigFactory factory = new MultipartConfigFactory();
MultipartConfigElement config = factory.createMultipartConfig();
assertThat(config.getLocation()).isEmpty();
assertThat(config.getMaxFileSize()).isEqualTo(-1L);
assertThat(config.getMaxRequestSize()).isEqualTo(-1L);
assertThat(config.getFileSizeThreshold()).isZero();
}
@Test
void createWithDataSizes() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize(DataSize.ofBytes(1));
factory.setMaxRequestSize(DataSize.ofKilobytes(2));
factory.setFileSizeThreshold(DataSize.ofMegabytes(3));
MultipartConfigElement config = factory.createMultipartConfig();
assertThat(config.getMaxFileSize()).isOne();
assertThat(config.getMaxRequestSize()).isEqualTo(2 * 1024L);
assertThat(config.getFileSizeThreshold()).isEqualTo(3 * 1024 * 1024);
}
@Test
void createWithNegativeDataSizes() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize(DataSize.ofBytes(-1));
factory.setMaxRequestSize(DataSize.ofKilobytes(-2));
factory.setFileSizeThreshold(DataSize.ofMegabytes(-3));
MultipartConfigElement config = factory.createMultipartConfig();
assertThat(config.getMaxFileSize()).isEqualTo(-1L);
assertThat(config.getMaxRequestSize()).isEqualTo(-1);
assertThat(config.getFileSizeThreshold()).isZero();
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.autoconfigure;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ServletManagementChildContextConfiguration}.
*
* @author Moritz Halbritter
*/
class ServletManagementChildContextConfigurationTests {
@Test // gh-45857
void doesNotCreateManagementServerPropertiesInChildContext() {
new WebApplicationContextRunner().withBean(ManagementServerProperties.class)
.run((parent) -> new WebApplicationContextRunner().withParent(parent)
.withUserConfiguration(ServletManagementChildContextConfiguration.class)
.run((context) -> assertThat(context.getBean(ManagementServerProperties.class))
.isSameAs(parent.getBean(ManagementServerProperties.class))));
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.mappings;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.boot.servlet.actuate.mappings.FiltersMappingDescriptionProvider.FiltersMappingDescriptionProviderRuntimeHints;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link FiltersMappingDescriptionProvider}.
*
* @author Moritz Halbritter
*/
class FiltersMappingDescriptionProviderTests {
@Test
void shouldRegisterHints() {
RuntimeHints runtimeHints = new RuntimeHints();
new FiltersMappingDescriptionProviderRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader());
assertThat(RuntimeHintsPredicates.reflection()
.onType(FilterRegistrationMappingDescription.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints);
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.actuate.mappings;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.boot.servlet.actuate.mappings.ServletsMappingDescriptionProvider.ServletsMappingDescriptionProviderRuntimeHints;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ServletsMappingDescriptionProvider}.
*
* @author Moritz Halbritter
*/
class ServletsMappingDescriptionProviderTests {
@Test
void shouldRegisterHints() {
RuntimeHints runtimeHints = new RuntimeHints();
new ServletsMappingDescriptionProviderRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader());
assertThat(RuntimeHintsPredicates.reflection()
.onType(ServletRegistrationMappingDescription.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints);
}
}