Create spring-boot-webmvc module
This commit is contained in:
committed by
Phillip Webb
parent
e83f4eed4e
commit
dfd5e56956
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.MultipartConfigElement;
|
||||
import jakarta.servlet.ServletRegistration;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
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.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.web.multipart.MultipartResolver;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for the Spring
|
||||
* {@link DispatcherServlet}. Should work for a standalone application where an embedded
|
||||
* web server is already present and also for a deployable application using
|
||||
* {@link SpringBootServletInitializer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @author Brian Clozel
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
|
||||
@AutoConfiguration
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ConditionalOnClass(DispatcherServlet.class)
|
||||
public class DispatcherServletAutoConfiguration {
|
||||
|
||||
/**
|
||||
* The bean name for a DispatcherServlet that will be mapped to the root URL "/".
|
||||
*/
|
||||
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
|
||||
|
||||
/**
|
||||
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/".
|
||||
*/
|
||||
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Conditional(DefaultDispatcherServletCondition.class)
|
||||
@ConditionalOnClass(ServletRegistration.class)
|
||||
@EnableConfigurationProperties(WebMvcProperties.class)
|
||||
protected static class DispatcherServletConfiguration {
|
||||
|
||||
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
|
||||
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
|
||||
DispatcherServlet dispatcherServlet = new DispatcherServlet();
|
||||
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
|
||||
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
|
||||
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
|
||||
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
|
||||
return dispatcherServlet;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(MultipartResolver.class)
|
||||
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
|
||||
public MultipartResolver multipartResolver(MultipartResolver resolver) {
|
||||
// Detect if the user has created a MultipartResolver but named it incorrectly
|
||||
return resolver;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Conditional(DispatcherServletRegistrationCondition.class)
|
||||
@ConditionalOnClass(ServletRegistration.class)
|
||||
@EnableConfigurationProperties(WebMvcProperties.class)
|
||||
@Import(DispatcherServletConfiguration.class)
|
||||
protected static class DispatcherServletRegistrationConfiguration {
|
||||
|
||||
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
|
||||
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
|
||||
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
|
||||
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
|
||||
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
|
||||
webMvcProperties.getServlet().getPath());
|
||||
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
|
||||
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
|
||||
multipartConfig.ifAvailable(registration::setMultipartConfig);
|
||||
return registration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Order(Ordered.LOWEST_PRECEDENCE - 10)
|
||||
private static final class DefaultDispatcherServletCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");
|
||||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
||||
List<String> dispatchServletBeans = Arrays
|
||||
.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
|
||||
if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
|
||||
return ConditionOutcome
|
||||
.noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
|
||||
}
|
||||
if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
|
||||
return ConditionOutcome
|
||||
.noMatch(message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
|
||||
}
|
||||
if (dispatchServletBeans.isEmpty()) {
|
||||
return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());
|
||||
}
|
||||
return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans")
|
||||
.items(Style.QUOTE, dispatchServletBeans)
|
||||
.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Order(Ordered.LOWEST_PRECEDENCE - 10)
|
||||
private static final class DispatcherServletRegistrationCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
||||
ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory);
|
||||
if (!outcome.isMatch()) {
|
||||
return outcome;
|
||||
}
|
||||
return checkServletRegistration(beanFactory);
|
||||
}
|
||||
|
||||
private ConditionOutcome checkDefaultDispatcherName(ConfigurableListableBeanFactory beanFactory) {
|
||||
boolean containsDispatcherBean = beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
|
||||
if (!containsDispatcherBean) {
|
||||
return ConditionOutcome.match();
|
||||
}
|
||||
List<String> servlets = Arrays
|
||||
.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
|
||||
if (!servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
|
||||
return ConditionOutcome.noMatch(
|
||||
startMessage().found("non dispatcher servlet").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
|
||||
}
|
||||
return ConditionOutcome.match();
|
||||
}
|
||||
|
||||
private ConditionOutcome checkServletRegistration(ConfigurableListableBeanFactory beanFactory) {
|
||||
ConditionMessage.Builder message = startMessage();
|
||||
List<String> registrations = Arrays
|
||||
.asList(beanFactory.getBeanNamesForType(ServletRegistrationBean.class, false, false));
|
||||
boolean containsDispatcherRegistrationBean = beanFactory
|
||||
.containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
|
||||
if (registrations.isEmpty()) {
|
||||
if (containsDispatcherRegistrationBean) {
|
||||
return ConditionOutcome.noMatch(message.found("non servlet registration bean")
|
||||
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
|
||||
}
|
||||
return ConditionOutcome.match(message.didNotFind("servlet registration bean").atAll());
|
||||
}
|
||||
if (registrations.contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) {
|
||||
return ConditionOutcome.noMatch(message.found("servlet registration bean")
|
||||
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
|
||||
}
|
||||
if (containsDispatcherRegistrationBean) {
|
||||
return ConditionOutcome.noMatch(message.found("non servlet registration bean")
|
||||
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
|
||||
}
|
||||
return ConditionOutcome.match(message.found("servlet registration beans")
|
||||
.items(Style.QUOTE, registrations)
|
||||
.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
|
||||
}
|
||||
|
||||
private ConditionMessage.Builder startMessage() {
|
||||
return ConditionMessage.forCondition("DispatcherServlet Registration");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure;
|
||||
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* Interface that can be used by auto-configurations that need path details for the
|
||||
* {@link DispatcherServletAutoConfiguration#DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME
|
||||
* default} {@link DispatcherServlet}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DispatcherServletPath {
|
||||
|
||||
/**
|
||||
* Returns the configured path of the dispatcher servlet.
|
||||
* @return the configured path
|
||||
*/
|
||||
String getPath();
|
||||
|
||||
/**
|
||||
* Return a form of the given path that's relative to the dispatcher servlet path.
|
||||
* @param path the path to make relative
|
||||
* @return the relative path
|
||||
*/
|
||||
default String getRelativePath(String path) {
|
||||
String prefix = getPrefix();
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
return prefix + path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a cleaned up version of the path that can be used as a prefix for URLs. The
|
||||
* resulting path will have path will not have a trailing slash.
|
||||
* @return the prefix
|
||||
* @see #getRelativePath(String)
|
||||
*/
|
||||
default String getPrefix() {
|
||||
String result = getPath();
|
||||
int index = result.indexOf('*');
|
||||
if (index != -1) {
|
||||
result = result.substring(0, index);
|
||||
}
|
||||
if (result.endsWith("/")) {
|
||||
result = result.substring(0, result.length() - 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a URL mapping pattern that can be used with a
|
||||
* {@link ServletRegistrationBean} to map the dispatcher servlet.
|
||||
* @return the path as a servlet URL mapping
|
||||
*/
|
||||
default String getServletUrlMapping() {
|
||||
if (getPath().isEmpty() || getPath().equals("/")) {
|
||||
return "/";
|
||||
}
|
||||
if (getPath().contains("*")) {
|
||||
return getPath();
|
||||
}
|
||||
if (getPath().endsWith("/")) {
|
||||
return getPath() + "*";
|
||||
}
|
||||
return getPath() + "/*";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.webmvc.autoconfigure;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* {@link ServletRegistrationBean} for the auto-configured {@link DispatcherServlet}. Both
|
||||
* registers the servlet and exposes {@link DispatcherServletPath} information.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet>
|
||||
implements DispatcherServletPath {
|
||||
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Create a new {@link DispatcherServletRegistrationBean} instance for the given
|
||||
* servlet and path.
|
||||
* @param servlet the dispatcher servlet
|
||||
* @param path the dispatcher servlet path
|
||||
*/
|
||||
public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
|
||||
super(servlet);
|
||||
Assert.notNull(path, "'path' must not be null");
|
||||
this.path = path;
|
||||
super.addUrlMappings(getServletUrlMapping());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUrlMappings(Collection<String> urlMappings) {
|
||||
throw new UnsupportedOperationException("URL Mapping cannot be changed on a DispatcherServlet registration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUrlMappings(String... urlMappings) {
|
||||
throw new UnsupportedOperationException("URL Mapping cannot be changed on a DispatcherServlet registration");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.webmvc.autoconfigure;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link TemplateAvailabilityProvider} that provides availability information for JSP
|
||||
* view templates.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class JspTemplateAvailabilityProvider implements TemplateAvailabilityProvider {
|
||||
|
||||
@Override
|
||||
public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader,
|
||||
ResourceLoader resourceLoader) {
|
||||
if (ClassUtils.isPresent("org.apache.jasper.compiler.JspConfig", classLoader)) {
|
||||
String resourceName = getResourceName(view, environment);
|
||||
if (resourceLoader.getResource(resourceName).exists()) {
|
||||
return true;
|
||||
}
|
||||
return new File("src/main/webapp", resourceName).exists();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getResourceName(String view, Environment environment) {
|
||||
String prefix = environment.getProperty("spring.mvc.view.prefix", WebMvcAutoConfiguration.DEFAULT_PREFIX);
|
||||
String suffix = environment.getProperty("spring.mvc.view.suffix", WebMvcAutoConfiguration.DEFAULT_SUFFIX);
|
||||
return prefix + view + suffix;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure;
|
||||
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
|
||||
/**
|
||||
* {@code @ControllerAdvice} annotated {@link ResponseEntityExceptionHandler} that is
|
||||
* auto-configured for problem details support.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
@ControllerAdvice
|
||||
class ProblemDetailsExceptionHandler extends ResponseEntityExceptionHandler {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,659 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.servlet.Servlet;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
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.ConditionalOnMissingFilterBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
|
||||
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties.Resources.Chain.Strategy;
|
||||
import org.springframework.boot.autoconfigure.web.WebResourcesRuntimeHints;
|
||||
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
|
||||
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.convert.ApplicationConversionService;
|
||||
import org.springframework.boot.http.autoconfigure.HttpMessageConverters;
|
||||
import org.springframework.boot.validation.autoconfigure.ValidatorAdapter;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter;
|
||||
import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter;
|
||||
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
|
||||
import org.springframework.boot.webmvc.autoconfigure.WebMvcProperties.Format;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.validation.DefaultMessageCodesResolver;
|
||||
import org.springframework.validation.MessageCodesResolver;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
import org.springframework.web.context.ServletContextAware;
|
||||
import org.springframework.web.context.request.RequestContextListener;
|
||||
import org.springframework.web.context.support.ServletContextResource;
|
||||
import org.springframework.web.filter.FormContentFilter;
|
||||
import org.springframework.web.filter.HiddenHttpMethodFilter;
|
||||
import org.springframework.web.filter.RequestContextFilter;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.FlashMapManager;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.RequestToViewNameTranslator;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.ViewResolver;
|
||||
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceChainRegistration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
|
||||
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
import org.springframework.web.servlet.resource.EncodedResourceResolver;
|
||||
import org.springframework.web.servlet.resource.ResourceResolver;
|
||||
import org.springframework.web.servlet.resource.ResourceUrlProvider;
|
||||
import org.springframework.web.servlet.resource.VersionResourceResolver;
|
||||
import org.springframework.web.servlet.view.BeanNameViewResolver;
|
||||
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
|
||||
import org.springframework.web.servlet.view.InternalResourceViewResolver;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebMvc Web MVC}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @author Sébastien Deleuze
|
||||
* @author Eddú Meléndez
|
||||
* @author Stephane Nicoll
|
||||
* @author Kristine Jetzke
|
||||
* @author Bruce Brouwer
|
||||
* @author Artsiom Yudovin
|
||||
* @author Scott Frederick
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class },
|
||||
afterName = "org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration")
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
|
||||
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
|
||||
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
|
||||
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
|
||||
public class WebMvcAutoConfiguration {
|
||||
|
||||
/**
|
||||
* The default Spring MVC view prefix.
|
||||
*/
|
||||
public static final String DEFAULT_PREFIX = "";
|
||||
|
||||
/**
|
||||
* The default Spring MVC view suffix.
|
||||
*/
|
||||
public static final String DEFAULT_SUFFIX = "";
|
||||
|
||||
private static final String SERVLET_LOCATION = "/";
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
|
||||
@ConditionalOnBooleanProperty("spring.mvc.hiddenmethod.filter.enabled")
|
||||
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
|
||||
return new OrderedHiddenHttpMethodFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(FormContentFilter.class)
|
||||
@ConditionalOnBooleanProperty(name = "spring.mvc.formcontent.filter.enabled", matchIfMissing = true)
|
||||
public OrderedFormContentFilter formContentFilter() {
|
||||
return new OrderedFormContentFilter();
|
||||
}
|
||||
|
||||
// Defined as a nested config to ensure WebMvcConfigurer is not read when not
|
||||
// on the classpath
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(EnableWebMvcConfiguration.class)
|
||||
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
|
||||
@Order(0)
|
||||
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
|
||||
|
||||
private final Resources resourceProperties;
|
||||
|
||||
private final WebMvcProperties mvcProperties;
|
||||
|
||||
private final ListableBeanFactory beanFactory;
|
||||
|
||||
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
|
||||
|
||||
private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;
|
||||
|
||||
private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;
|
||||
|
||||
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
|
||||
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
|
||||
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
|
||||
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
|
||||
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
|
||||
this.resourceProperties = webProperties.getResources();
|
||||
this.mvcProperties = mvcProperties;
|
||||
this.beanFactory = beanFactory;
|
||||
this.messageConvertersProvider = messageConvertersProvider;
|
||||
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
|
||||
this.dispatcherServletPath = dispatcherServletPath;
|
||||
this.servletRegistrations = servletRegistrations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServletContext(ServletContext servletContext) {
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
this.messageConvertersProvider
|
||||
.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
|
||||
if (this.beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) {
|
||||
Object taskExecutor = this.beanFactory
|
||||
.getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
|
||||
if (taskExecutor instanceof AsyncTaskExecutor asyncTaskExecutor) {
|
||||
configurer.setTaskExecutor(asyncTaskExecutor);
|
||||
}
|
||||
}
|
||||
Duration timeout = this.mvcProperties.getAsync().getRequestTimeout();
|
||||
if (timeout != null) {
|
||||
configurer.setDefaultTimeout(timeout.toMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("removal")
|
||||
public void configurePathMatch(PathMatchConfigurer configurer) {
|
||||
if (this.mvcProperties.getPathmatch()
|
||||
.getMatchingStrategy() == WebMvcProperties.MatchingStrategy.ANT_PATH_MATCHER) {
|
||||
configurer.setPathMatcher(new AntPathMatcher());
|
||||
this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
|
||||
String servletUrlMapping = dispatcherPath.getServletUrlMapping();
|
||||
if (servletUrlMapping.equals("/") && singleDispatcherServlet()) {
|
||||
UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
urlPathHelper.setAlwaysUseFullPath(true);
|
||||
configurer.setUrlPathHelper(urlPathHelper);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean singleDispatcherServlet() {
|
||||
return this.servletRegistrations.stream()
|
||||
.map(ServletRegistrationBean::getServlet)
|
||||
.filter(DispatcherServlet.class::isInstance)
|
||||
.count() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties.getContentnegotiation();
|
||||
configurer.favorParameter(contentnegotiation.isFavorParameter());
|
||||
if (contentnegotiation.getParameterName() != null) {
|
||||
configurer.parameterName(contentnegotiation.getParameterName());
|
||||
}
|
||||
Map<String, MediaType> mediaTypes = contentnegotiation.getMediaTypes();
|
||||
mediaTypes.forEach(configurer::mediaType);
|
||||
List<MediaType> defaultContentTypes = contentnegotiation.getDefaultContentTypes();
|
||||
if (!CollectionUtils.isEmpty(defaultContentTypes)) {
|
||||
configurer.defaultContentType(defaultContentTypes.toArray(new MediaType[0]));
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public InternalResourceViewResolver defaultViewResolver() {
|
||||
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
|
||||
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
|
||||
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
|
||||
return resolver;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(View.class)
|
||||
@ConditionalOnMissingBean
|
||||
public BeanNameViewResolver beanNameViewResolver() {
|
||||
BeanNameViewResolver resolver = new BeanNameViewResolver();
|
||||
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
|
||||
return resolver;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(ViewResolver.class)
|
||||
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
|
||||
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
|
||||
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
|
||||
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
|
||||
// ContentNegotiatingViewResolver uses all the other view resolvers to locate
|
||||
// a view so it should have a high precedence
|
||||
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||
return resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageCodesResolver getMessageCodesResolver() {
|
||||
if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
|
||||
DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
|
||||
resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat());
|
||||
return resolver;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
ApplicationConversionService.addBeans(registry, this.beanFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
if (!this.resourceProperties.isAddMappings()) {
|
||||
logger.debug("Default resource handling disabled");
|
||||
return;
|
||||
}
|
||||
addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(),
|
||||
"classpath:/META-INF/resources/webjars/");
|
||||
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
|
||||
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
|
||||
if (this.servletContext != null) {
|
||||
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
|
||||
registration.addResourceLocations(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
|
||||
addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
|
||||
}
|
||||
|
||||
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern,
|
||||
Consumer<ResourceHandlerRegistration> customizer) {
|
||||
if (registry.hasMappingForPattern(pattern)) {
|
||||
return;
|
||||
}
|
||||
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
|
||||
customizer.accept(registration);
|
||||
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
|
||||
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
|
||||
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
|
||||
customizeResourceHandlerRegistration(registration);
|
||||
}
|
||||
|
||||
private Integer getSeconds(Duration cachePeriod) {
|
||||
return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
|
||||
}
|
||||
|
||||
private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
|
||||
if (this.resourceHandlerRegistrationCustomizer != null) {
|
||||
this.resourceHandlerRegistrationCustomizer.customize(registration);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class })
|
||||
@ConditionalOnMissingFilterBean
|
||||
public static RequestContextFilter requestContextFilter() {
|
||||
return new OrderedRequestContextFilter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration equivalent to {@code @EnableWebMvc}.
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(WebProperties.class)
|
||||
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
|
||||
|
||||
private final Resources resourceProperties;
|
||||
|
||||
private final WebMvcProperties mvcProperties;
|
||||
|
||||
private final WebProperties webProperties;
|
||||
|
||||
private final ListableBeanFactory beanFactory;
|
||||
|
||||
private final WebMvcRegistrations mvcRegistrations;
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
public EnableWebMvcConfiguration(WebMvcProperties mvcProperties, WebProperties webProperties,
|
||||
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
|
||||
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
|
||||
ListableBeanFactory beanFactory) {
|
||||
this.resourceProperties = webProperties.getResources();
|
||||
this.mvcProperties = mvcProperties;
|
||||
this.webProperties = webProperties;
|
||||
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
|
||||
if (this.mvcRegistrations != null) {
|
||||
RequestMappingHandlerAdapter adapter = this.mvcRegistrations.getRequestMappingHandlerAdapter();
|
||||
if (adapter != null) {
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
return super.createRequestMappingHandlerAdapter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
|
||||
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
|
||||
return createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider,
|
||||
WelcomePageHandlerMapping::new);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WelcomePageNotAcceptableHandlerMapping welcomePageNotAcceptableHandlerMapping(
|
||||
ApplicationContext applicationContext, FormattingConversionService mvcConversionService,
|
||||
ResourceUrlProvider mvcResourceUrlProvider) {
|
||||
return createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider,
|
||||
WelcomePageNotAcceptableHandlerMapping::new);
|
||||
}
|
||||
|
||||
private <T extends AbstractUrlHandlerMapping> T createWelcomePageHandlerMapping(
|
||||
ApplicationContext applicationContext, FormattingConversionService mvcConversionService,
|
||||
ResourceUrlProvider mvcResourceUrlProvider, WelcomePageHandlerMappingFactory<T> factory) {
|
||||
TemplateAvailabilityProviders templateAvailabilityProviders = new TemplateAvailabilityProviders(
|
||||
applicationContext);
|
||||
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
|
||||
T handlerMapping = factory.create(templateAvailabilityProviders, applicationContext, getIndexHtmlResource(),
|
||||
staticPathPattern);
|
||||
handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
|
||||
handlerMapping.setCorsConfigurations(getCorsConfigurations());
|
||||
return handlerMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
|
||||
public LocaleResolver localeResolver() {
|
||||
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
|
||||
return new FixedLocaleResolver(this.webProperties.getLocale());
|
||||
}
|
||||
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
|
||||
localeResolver.setDefaultLocale(this.webProperties.getLocale());
|
||||
return localeResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME)
|
||||
public FlashMapManager flashMapManager() {
|
||||
return super.flashMapManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME)
|
||||
public RequestToViewNameTranslator viewNameTranslator() {
|
||||
return super.viewNameTranslator();
|
||||
}
|
||||
|
||||
private Resource getIndexHtmlResource() {
|
||||
for (String location : this.resourceProperties.getStaticLocations()) {
|
||||
Resource indexHtml = getIndexHtmlResource(location);
|
||||
if (indexHtml != null) {
|
||||
return indexHtml;
|
||||
}
|
||||
}
|
||||
ServletContext servletContext = getServletContext();
|
||||
if (servletContext != null) {
|
||||
return getIndexHtmlResource(new ServletContextResource(servletContext, SERVLET_LOCATION));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Resource getIndexHtmlResource(String location) {
|
||||
return getIndexHtmlResource(this.resourceLoader.getResource(location));
|
||||
}
|
||||
|
||||
private Resource getIndexHtmlResource(Resource location) {
|
||||
try {
|
||||
Resource resource = location.createRelative("index.html");
|
||||
if (resource.exists() && (resource.getURL() != null)) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public FormattingConversionService mvcConversionService() {
|
||||
Format format = this.mvcProperties.getFormat();
|
||||
WebConversionService conversionService = new WebConversionService(
|
||||
new DateTimeFormatters().dateFormat(format.getDate())
|
||||
.timeFormat(format.getTime())
|
||||
.dateTimeFormat(format.getDateTime()));
|
||||
addFormatters(conversionService);
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public Validator mvcValidator() {
|
||||
if (!ClassUtils.isPresent("jakarta.validation.Validator", getClass().getClassLoader())
|
||||
|| !ClassUtils.isPresent("org.springframework.boot.validation.autoconfigure.ValidatorAdapter",
|
||||
getClass().getClassLoader())) {
|
||||
return super.mvcValidator();
|
||||
}
|
||||
return ValidatorAdapter.get(getApplicationContext(), getValidator());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
|
||||
if (this.mvcRegistrations != null) {
|
||||
RequestMappingHandlerMapping mapping = this.mvcRegistrations.getRequestMappingHandlerMapping();
|
||||
if (mapping != null) {
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
return super.createRequestMappingHandlerMapping();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(
|
||||
FormattingConversionService mvcConversionService, Validator mvcValidator) {
|
||||
try {
|
||||
return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
|
||||
if (this.mvcRegistrations != null) {
|
||||
ExceptionHandlerExceptionResolver resolver = this.mvcRegistrations
|
||||
.getExceptionHandlerExceptionResolver();
|
||||
if (resolver != null) {
|
||||
return resolver;
|
||||
}
|
||||
}
|
||||
return super.createExceptionHandlerExceptionResolver();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
|
||||
super.extendHandlerExceptionResolvers(exceptionResolvers);
|
||||
if (this.mvcProperties.isLogResolvedException()) {
|
||||
for (HandlerExceptionResolver resolver : exceptionResolvers) {
|
||||
if (resolver instanceof AbstractHandlerExceptionResolver abstractResolver) {
|
||||
abstractResolver.setWarnLogCategory(resolver.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnEnabledResourceChain
|
||||
static class ResourceChainCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer(
|
||||
WebProperties webProperties) {
|
||||
return new ResourceChainResourceHandlerRegistrationCustomizer(webProperties.getResources());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface WelcomePageHandlerMappingFactory<T extends AbstractUrlHandlerMapping> {
|
||||
|
||||
T create(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext,
|
||||
Resource indexHtmlResource, String staticPathPattern);
|
||||
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ResourceHandlerRegistrationCustomizer {
|
||||
|
||||
void customize(ResourceHandlerRegistration registration);
|
||||
|
||||
}
|
||||
|
||||
static class ResourceChainResourceHandlerRegistrationCustomizer implements ResourceHandlerRegistrationCustomizer {
|
||||
|
||||
private final Resources resourceProperties;
|
||||
|
||||
ResourceChainResourceHandlerRegistrationCustomizer(Resources resourceProperties) {
|
||||
this.resourceProperties = resourceProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ResourceHandlerRegistration registration) {
|
||||
Resources.Chain properties = this.resourceProperties.getChain();
|
||||
configureResourceChain(properties, registration.resourceChain(properties.isCache()));
|
||||
}
|
||||
|
||||
private void configureResourceChain(Resources.Chain properties, ResourceChainRegistration chain) {
|
||||
Strategy strategy = properties.getStrategy();
|
||||
if (properties.isCompressed()) {
|
||||
chain.addResolver(new EncodedResourceResolver());
|
||||
}
|
||||
if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) {
|
||||
chain.addResolver(getVersionResourceResolver(strategy));
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceResolver getVersionResourceResolver(Strategy properties) {
|
||||
VersionResourceResolver resolver = new VersionResourceResolver();
|
||||
if (properties.getFixed().isEnabled()) {
|
||||
String version = properties.getFixed().getVersion();
|
||||
String[] paths = properties.getFixed().getPaths();
|
||||
resolver.addFixedVersionStrategy(version, paths);
|
||||
}
|
||||
if (properties.getContent().isEnabled()) {
|
||||
String[] paths = properties.getContent().getPaths();
|
||||
resolver.addContentVersionStrategy(paths);
|
||||
}
|
||||
return resolver;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBooleanProperty("spring.mvc.problemdetails.enabled")
|
||||
static class ProblemDetailsErrorHandlingConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ResponseEntityExceptionHandler.class)
|
||||
@Order(0)
|
||||
ProblemDetailsExceptionHandler problemDetailsExceptionHandler() {
|
||||
return new ProblemDetailsExceptionHandler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.DefaultMessageCodesResolver;
|
||||
|
||||
/**
|
||||
* {@link ConfigurationProperties Properties} for Spring MVC.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Sébastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
* @author Eddú Meléndez
|
||||
* @author Brian Clozel
|
||||
* @author Vedran Pavic
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.mvc")
|
||||
public class WebMvcProperties {
|
||||
|
||||
/**
|
||||
* Formatting strategy for message codes. For instance, 'PREFIX_ERROR_CODE'.
|
||||
*/
|
||||
private DefaultMessageCodesResolver.Format messageCodesResolverFormat;
|
||||
|
||||
private final Format format = new Format();
|
||||
|
||||
/**
|
||||
* Whether to dispatch TRACE requests to the FrameworkServlet doService method.
|
||||
*/
|
||||
private boolean dispatchTraceRequest = false;
|
||||
|
||||
/**
|
||||
* Whether to dispatch OPTIONS requests to the FrameworkServlet doService method.
|
||||
*/
|
||||
private boolean dispatchOptionsRequest = true;
|
||||
|
||||
/**
|
||||
* Whether to publish a ServletRequestHandledEvent at the end of each request.
|
||||
*/
|
||||
private boolean publishRequestHandledEvents = true;
|
||||
|
||||
/**
|
||||
* Whether logging of (potentially sensitive) request details at DEBUG and TRACE level
|
||||
* is allowed.
|
||||
*/
|
||||
private boolean logRequestDetails;
|
||||
|
||||
/**
|
||||
* Whether to enable warn logging of exceptions resolved by a
|
||||
* "HandlerExceptionResolver", except for "DefaultHandlerExceptionResolver".
|
||||
*/
|
||||
private boolean logResolvedException = false;
|
||||
|
||||
/**
|
||||
* Path pattern used for static resources.
|
||||
*/
|
||||
private String staticPathPattern = "/**";
|
||||
|
||||
/**
|
||||
* Path pattern used for WebJar assets.
|
||||
*/
|
||||
private String webjarsPathPattern = "/webjars/**";
|
||||
|
||||
private final Async async = new Async();
|
||||
|
||||
private final Servlet servlet = new Servlet();
|
||||
|
||||
private final View view = new View();
|
||||
|
||||
private final Contentnegotiation contentnegotiation = new Contentnegotiation();
|
||||
|
||||
private final Pathmatch pathmatch = new Pathmatch();
|
||||
|
||||
private final Problemdetails problemdetails = new Problemdetails();
|
||||
|
||||
public DefaultMessageCodesResolver.Format getMessageCodesResolverFormat() {
|
||||
return this.messageCodesResolverFormat;
|
||||
}
|
||||
|
||||
public void setMessageCodesResolverFormat(DefaultMessageCodesResolver.Format messageCodesResolverFormat) {
|
||||
this.messageCodesResolverFormat = messageCodesResolverFormat;
|
||||
}
|
||||
|
||||
public Format getFormat() {
|
||||
return this.format;
|
||||
}
|
||||
|
||||
public boolean isPublishRequestHandledEvents() {
|
||||
return this.publishRequestHandledEvents;
|
||||
}
|
||||
|
||||
public void setPublishRequestHandledEvents(boolean publishRequestHandledEvents) {
|
||||
this.publishRequestHandledEvents = publishRequestHandledEvents;
|
||||
}
|
||||
|
||||
public boolean isLogRequestDetails() {
|
||||
return this.logRequestDetails;
|
||||
}
|
||||
|
||||
public void setLogRequestDetails(boolean logRequestDetails) {
|
||||
this.logRequestDetails = logRequestDetails;
|
||||
}
|
||||
|
||||
public boolean isLogResolvedException() {
|
||||
return this.logResolvedException;
|
||||
}
|
||||
|
||||
public void setLogResolvedException(boolean logResolvedException) {
|
||||
this.logResolvedException = logResolvedException;
|
||||
}
|
||||
|
||||
public boolean isDispatchOptionsRequest() {
|
||||
return this.dispatchOptionsRequest;
|
||||
}
|
||||
|
||||
public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
|
||||
this.dispatchOptionsRequest = dispatchOptionsRequest;
|
||||
}
|
||||
|
||||
public boolean isDispatchTraceRequest() {
|
||||
return this.dispatchTraceRequest;
|
||||
}
|
||||
|
||||
public void setDispatchTraceRequest(boolean dispatchTraceRequest) {
|
||||
this.dispatchTraceRequest = dispatchTraceRequest;
|
||||
}
|
||||
|
||||
public String getStaticPathPattern() {
|
||||
return this.staticPathPattern;
|
||||
}
|
||||
|
||||
public void setStaticPathPattern(String staticPathPattern) {
|
||||
this.staticPathPattern = staticPathPattern;
|
||||
}
|
||||
|
||||
public String getWebjarsPathPattern() {
|
||||
return this.webjarsPathPattern;
|
||||
}
|
||||
|
||||
public void setWebjarsPathPattern(String webjarsPathPattern) {
|
||||
this.webjarsPathPattern = webjarsPathPattern;
|
||||
}
|
||||
|
||||
public Async getAsync() {
|
||||
return this.async;
|
||||
}
|
||||
|
||||
public Servlet getServlet() {
|
||||
return this.servlet;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return this.view;
|
||||
}
|
||||
|
||||
public Contentnegotiation getContentnegotiation() {
|
||||
return this.contentnegotiation;
|
||||
}
|
||||
|
||||
public Pathmatch getPathmatch() {
|
||||
return this.pathmatch;
|
||||
}
|
||||
|
||||
public Problemdetails getProblemdetails() {
|
||||
return this.problemdetails;
|
||||
}
|
||||
|
||||
public static class Async {
|
||||
|
||||
/**
|
||||
* Amount of time before asynchronous request handling times out. If this value is
|
||||
* not set, the default timeout of the underlying implementation is used.
|
||||
*/
|
||||
private Duration requestTimeout;
|
||||
|
||||
public Duration getRequestTimeout() {
|
||||
return this.requestTimeout;
|
||||
}
|
||||
|
||||
public void setRequestTimeout(Duration requestTimeout) {
|
||||
this.requestTimeout = requestTimeout;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Servlet {
|
||||
|
||||
/**
|
||||
* Path of the dispatcher servlet. Setting a custom value for this property is not
|
||||
* compatible with the PathPatternParser matching strategy.
|
||||
*/
|
||||
private String path = "/";
|
||||
|
||||
/**
|
||||
* Load on startup priority of the dispatcher servlet.
|
||||
*/
|
||||
private int loadOnStartup = -1;
|
||||
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
Assert.notNull(path, "'path' must not be null");
|
||||
Assert.isTrue(!path.contains("*"), "'path' must not contain wildcards");
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public int getLoadOnStartup() {
|
||||
return this.loadOnStartup;
|
||||
}
|
||||
|
||||
public void setLoadOnStartup(int loadOnStartup) {
|
||||
this.loadOnStartup = loadOnStartup;
|
||||
}
|
||||
|
||||
public String getServletMapping() {
|
||||
if (this.path.isEmpty() || this.path.equals("/")) {
|
||||
return "/";
|
||||
}
|
||||
if (this.path.endsWith("/")) {
|
||||
return this.path + "*";
|
||||
}
|
||||
return this.path + "/*";
|
||||
}
|
||||
|
||||
public String getPath(String path) {
|
||||
String prefix = getServletPrefix();
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
return prefix + path;
|
||||
}
|
||||
|
||||
public String getServletPrefix() {
|
||||
String result = this.path;
|
||||
int index = result.indexOf('*');
|
||||
if (index != -1) {
|
||||
result = result.substring(0, index);
|
||||
}
|
||||
if (result.endsWith("/")) {
|
||||
result = result.substring(0, result.length() - 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class View {
|
||||
|
||||
/**
|
||||
* Spring MVC view prefix.
|
||||
*/
|
||||
private String prefix;
|
||||
|
||||
/**
|
||||
* Spring MVC view suffix.
|
||||
*/
|
||||
private String suffix;
|
||||
|
||||
public String getPrefix() {
|
||||
return this.prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public String getSuffix() {
|
||||
return this.suffix;
|
||||
}
|
||||
|
||||
public void setSuffix(String suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Contentnegotiation {
|
||||
|
||||
/**
|
||||
* Whether a request parameter ("format" by default) should be used to determine
|
||||
* the requested media type.
|
||||
*/
|
||||
private boolean favorParameter = false;
|
||||
|
||||
/**
|
||||
* Query parameter name to use when "favor-parameter" is enabled.
|
||||
*/
|
||||
private String parameterName;
|
||||
|
||||
/**
|
||||
* Map file extensions to media types for content negotiation. For instance, yml
|
||||
* to text/yaml.
|
||||
*/
|
||||
private Map<String, MediaType> mediaTypes = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* List of default content types to be used when no specific content type is
|
||||
* requested.
|
||||
*/
|
||||
private List<MediaType> defaultContentTypes = new ArrayList<>();
|
||||
|
||||
public boolean isFavorParameter() {
|
||||
return this.favorParameter;
|
||||
}
|
||||
|
||||
public void setFavorParameter(boolean favorParameter) {
|
||||
this.favorParameter = favorParameter;
|
||||
}
|
||||
|
||||
public String getParameterName() {
|
||||
return this.parameterName;
|
||||
}
|
||||
|
||||
public void setParameterName(String parameterName) {
|
||||
this.parameterName = parameterName;
|
||||
}
|
||||
|
||||
public Map<String, MediaType> getMediaTypes() {
|
||||
return this.mediaTypes;
|
||||
}
|
||||
|
||||
public void setMediaTypes(Map<String, MediaType> mediaTypes) {
|
||||
this.mediaTypes = mediaTypes;
|
||||
}
|
||||
|
||||
public List<MediaType> getDefaultContentTypes() {
|
||||
return this.defaultContentTypes;
|
||||
}
|
||||
|
||||
public void setDefaultContentTypes(List<MediaType> defaultContentTypes) {
|
||||
this.defaultContentTypes = defaultContentTypes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Pathmatch {
|
||||
|
||||
/**
|
||||
* Choice of strategy for matching request paths against registered mappings.
|
||||
*/
|
||||
private MatchingStrategy matchingStrategy = MatchingStrategy.PATH_PATTERN_PARSER;
|
||||
|
||||
public MatchingStrategy getMatchingStrategy() {
|
||||
return this.matchingStrategy;
|
||||
}
|
||||
|
||||
public void setMatchingStrategy(MatchingStrategy matchingStrategy) {
|
||||
this.matchingStrategy = matchingStrategy;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Format {
|
||||
|
||||
/**
|
||||
* Date format to use, for example 'dd/MM/yyyy'. Used for formatting of
|
||||
* java.util.Date and java.time.LocalDate.
|
||||
*/
|
||||
private String date;
|
||||
|
||||
/**
|
||||
* Time format to use, for example 'HH:mm:ss'. Used for formatting of java.time's
|
||||
* LocalTime and OffsetTime.
|
||||
*/
|
||||
private String time;
|
||||
|
||||
/**
|
||||
* Date-time format to use, for example 'yyyy-MM-dd HH:mm:ss'. Used for formatting
|
||||
* of java.time's LocalDateTime, OffsetDateTime, and ZonedDateTime.
|
||||
*/
|
||||
private String dateTime;
|
||||
|
||||
public String getDate() {
|
||||
return this.date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getTime() {
|
||||
return this.time;
|
||||
}
|
||||
|
||||
public void setTime(String time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getDateTime() {
|
||||
return this.dateTime;
|
||||
}
|
||||
|
||||
public void setDateTime(String dateTime) {
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Matching strategy options.
|
||||
*
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public enum MatchingStrategy {
|
||||
|
||||
/**
|
||||
* Use the {@code AntPathMatcher} implementation.
|
||||
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of
|
||||
* {@link #PATH_PATTERN_PARSER}
|
||||
*/
|
||||
@Deprecated(since = "4.0.0", forRemoval = true)
|
||||
ANT_PATH_MATCHER,
|
||||
|
||||
/**
|
||||
* Use the {@code PathPatternParser} implementation.
|
||||
*/
|
||||
PATH_PATTERN_PARSER
|
||||
|
||||
}
|
||||
|
||||
public static class Problemdetails {
|
||||
|
||||
/**
|
||||
* Whether RFC 9457 Problem Details support should be enabled.
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure;
|
||||
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
/**
|
||||
* Interface to register key components of the {@link WebMvcConfigurationSupport} in place
|
||||
* of the default ones provided by Spring MVC.
|
||||
* <p>
|
||||
* All custom instances are later processed by Boot and Spring MVC configurations. To
|
||||
* participate in, and if desired, override that subsequent processing,
|
||||
* {@link WebMvcConfigurer} should be used.
|
||||
* <p>
|
||||
* A single instance of this component should be registered, otherwise making it
|
||||
* impossible to choose from redundant MVC components.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 4.0.0
|
||||
* @see org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration.EnableWebMvcConfiguration
|
||||
*/
|
||||
public interface WebMvcRegistrations {
|
||||
|
||||
/**
|
||||
* Return the custom {@link RequestMappingHandlerMapping} that should be used and
|
||||
* processed by the MVC configuration.
|
||||
* @return the custom {@link RequestMappingHandlerMapping} instance
|
||||
*/
|
||||
default RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the custom {@link RequestMappingHandlerAdapter} that should be used and
|
||||
* processed by the MVC configuration.
|
||||
* @return the custom {@link RequestMappingHandlerAdapter} instance
|
||||
*/
|
||||
default RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the custom {@link ExceptionHandlerExceptionResolver} that should be used and
|
||||
* processed by the MVC configuration.
|
||||
* @return the custom {@link ExceptionHandlerExceptionResolver} instance
|
||||
*/
|
||||
default ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.webmvc.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Details for a welcome page resolved from a resource or a template.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
final class WelcomePage {
|
||||
|
||||
/**
|
||||
* Value used for an unresolved welcome page.
|
||||
*/
|
||||
static final WelcomePage UNRESOLVED = new WelcomePage(null, false);
|
||||
|
||||
private final String viewName;
|
||||
|
||||
private final boolean templated;
|
||||
|
||||
private WelcomePage(String viewName, boolean templated) {
|
||||
this.viewName = viewName;
|
||||
this.templated = templated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the view name of the welcome page.
|
||||
* @return the view name
|
||||
*/
|
||||
String getViewName() {
|
||||
return this.viewName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the welcome page is from a template.
|
||||
* @return if the welcome page is templated
|
||||
*/
|
||||
boolean isTemplated() {
|
||||
return this.templated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the {@link WelcomePage} to use.
|
||||
* @param templateAvailabilityProviders the template availability providers
|
||||
* @param applicationContext the application context
|
||||
* @param indexHtmlResource the index HTML resource to use or {@code null}
|
||||
* @param staticPathPattern the static path pattern being used
|
||||
* @return a resolved {@link WelcomePage} instance or {@link #UNRESOLVED}
|
||||
*/
|
||||
static WelcomePage resolve(TemplateAvailabilityProviders templateAvailabilityProviders,
|
||||
ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) {
|
||||
if (indexHtmlResource != null && "/**".equals(staticPathPattern)) {
|
||||
return new WelcomePage("forward:index.html", false);
|
||||
}
|
||||
if (templateAvailabilityProviders.getProvider("index", applicationContext) != null) {
|
||||
return new WelcomePage("index", true);
|
||||
}
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.InvalidMediaTypeException;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.ParameterizableViewController;
|
||||
|
||||
/**
|
||||
* An {@link AbstractUrlHandlerMapping} for an application's HTML welcome page. Supports
|
||||
* both static and templated files. If both a static and templated index page are
|
||||
* available, the static page is preferred.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Bruce Brouwer
|
||||
* @author Moritz Halbritter
|
||||
* @see WelcomePageNotAcceptableHandlerMapping
|
||||
*/
|
||||
final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(WelcomePageHandlerMapping.class);
|
||||
|
||||
private static final List<MediaType> MEDIA_TYPES_ALL = Collections.singletonList(MediaType.ALL);
|
||||
|
||||
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
|
||||
ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) {
|
||||
setOrder(2);
|
||||
WelcomePage welcomePage = WelcomePage.resolve(templateAvailabilityProviders, applicationContext,
|
||||
indexHtmlResource, staticPathPattern);
|
||||
if (welcomePage != WelcomePage.UNRESOLVED) {
|
||||
logger.info(LogMessage.of(() -> (!welcomePage.isTemplated()) ? "Adding welcome page: " + indexHtmlResource
|
||||
: "Adding welcome page template: index"));
|
||||
ParameterizableViewController controller = new ParameterizableViewController();
|
||||
controller.setViewName(welcomePage.getViewName());
|
||||
setRootHandler(controller);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHandlerInternal(HttpServletRequest request) throws Exception {
|
||||
return (!isHtmlTextAccepted(request)) ? null : super.getHandlerInternal(request);
|
||||
}
|
||||
|
||||
private boolean isHtmlTextAccepted(HttpServletRequest request) {
|
||||
for (MediaType mediaType : getAcceptedMediaTypes(request)) {
|
||||
if (mediaType.includes(MediaType.TEXT_HTML)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) {
|
||||
String acceptHeader = request.getHeader(HttpHeaders.ACCEPT);
|
||||
if (StringUtils.hasText(acceptHeader)) {
|
||||
try {
|
||||
return MediaType.parseMediaTypes(acceptHeader);
|
||||
}
|
||||
catch (InvalidMediaTypeException ex) {
|
||||
logger.warn("Received invalid Accept header. Assuming all media types are accepted",
|
||||
logger.isDebugEnabled() ? ex : null);
|
||||
}
|
||||
}
|
||||
return MEDIA_TYPES_ALL;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.Controller;
|
||||
|
||||
/**
|
||||
* An {@link AbstractUrlHandlerMapping} for an application's welcome page that was
|
||||
* ultimately not accepted.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class WelcomePageNotAcceptableHandlerMapping extends AbstractUrlHandlerMapping {
|
||||
|
||||
WelcomePageNotAcceptableHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
|
||||
ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) {
|
||||
setOrder(LOWEST_PRECEDENCE - 10); // Before ResourceHandlerRegistry
|
||||
WelcomePage welcomePage = WelcomePage.resolve(templateAvailabilityProviders, applicationContext,
|
||||
indexHtmlResource, staticPathPattern);
|
||||
if (welcomePage != WelcomePage.UNRESOLVED) {
|
||||
setRootHandler((Controller) this::handleRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||
response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
|
||||
return super.getHandlerInternal(request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure.error;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.web.error.ErrorAttributeOptions;
|
||||
import org.springframework.boot.webmvc.error.ErrorAttributes;
|
||||
import org.springframework.boot.webmvc.error.ErrorController;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* Abstract base class for error {@link Controller @Controller} implementations.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Moritz Halbritter
|
||||
* @since 4.0.0
|
||||
* @see ErrorAttributes
|
||||
*/
|
||||
public abstract class AbstractErrorController implements ErrorController {
|
||||
|
||||
private final ErrorAttributes errorAttributes;
|
||||
|
||||
private final List<ErrorViewResolver> errorViewResolvers;
|
||||
|
||||
public AbstractErrorController(ErrorAttributes errorAttributes) {
|
||||
this(errorAttributes, null);
|
||||
}
|
||||
|
||||
public AbstractErrorController(ErrorAttributes errorAttributes, List<ErrorViewResolver> errorViewResolvers) {
|
||||
Assert.notNull(errorAttributes, "'errorAttributes' must not be null");
|
||||
this.errorAttributes = errorAttributes;
|
||||
this.errorViewResolvers = sortErrorViewResolvers(errorViewResolvers);
|
||||
}
|
||||
|
||||
private List<ErrorViewResolver> sortErrorViewResolvers(List<ErrorViewResolver> resolvers) {
|
||||
List<ErrorViewResolver> sorted = new ArrayList<>();
|
||||
if (resolvers != null) {
|
||||
sorted.addAll(resolvers);
|
||||
AnnotationAwareOrderComparator.sortIfNecessary(sorted);
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, ErrorAttributeOptions options) {
|
||||
WebRequest webRequest = new ServletWebRequest(request);
|
||||
return this.errorAttributes.getErrorAttributes(webRequest, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the trace parameter is set.
|
||||
* @param request the request
|
||||
* @return whether the trace parameter is set
|
||||
*/
|
||||
protected boolean getTraceParameter(HttpServletRequest request) {
|
||||
return getBooleanParameter(request, "trace");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the message parameter is set.
|
||||
* @param request the request
|
||||
* @return whether the message parameter is set
|
||||
*/
|
||||
protected boolean getMessageParameter(HttpServletRequest request) {
|
||||
return getBooleanParameter(request, "message");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the errors parameter is set.
|
||||
* @param request the request
|
||||
* @return whether the errors parameter is set
|
||||
*/
|
||||
protected boolean getErrorsParameter(HttpServletRequest request) {
|
||||
return getBooleanParameter(request, "errors");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the path parameter is set.
|
||||
* @param request the request
|
||||
* @return whether the path parameter is set
|
||||
*/
|
||||
protected boolean getPathParameter(HttpServletRequest request) {
|
||||
return getBooleanParameter(request, "path");
|
||||
}
|
||||
|
||||
protected boolean getBooleanParameter(HttpServletRequest request, String parameterName) {
|
||||
String parameter = request.getParameter(parameterName);
|
||||
if (parameter == null) {
|
||||
return false;
|
||||
}
|
||||
return !"false".equalsIgnoreCase(parameter);
|
||||
}
|
||||
|
||||
protected HttpStatus getStatus(HttpServletRequest request) {
|
||||
Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
|
||||
if (statusCode == null) {
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
try {
|
||||
return HttpStatus.valueOf(statusCode);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve any specific error views. By default this method delegates to
|
||||
* {@link ErrorViewResolver ErrorViewResolvers}.
|
||||
* @param request the request
|
||||
* @param response the response
|
||||
* @param status the HTTP status
|
||||
* @param model the suggested model
|
||||
* @return a specific {@link ModelAndView} or {@code null} if the default should be
|
||||
* used
|
||||
*/
|
||||
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
|
||||
Map<String, Object> model) {
|
||||
for (ErrorViewResolver resolver : this.errorViewResolvers) {
|
||||
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
|
||||
if (modelAndView != null) {
|
||||
return modelAndView;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure.error;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ErrorProperties;
|
||||
import org.springframework.boot.web.error.ErrorAttributeOptions;
|
||||
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
|
||||
import org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory;
|
||||
import org.springframework.boot.webmvc.error.ErrorAttributes;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* Basic global error {@link Controller @Controller}, rendering {@link ErrorAttributes}.
|
||||
* More specific errors can be handled either using Spring MVC abstractions (e.g.
|
||||
* {@code @ExceptionHandler}) or by adding servlet
|
||||
* {@link ConfigurableServletWebServerFactory#setErrorPages server error pages}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Michael Stummvoll
|
||||
* @author Stephane Nicoll
|
||||
* @author Scott Frederick
|
||||
* @author Moritz Halbritter
|
||||
* @since 4.0.0
|
||||
* @see ErrorAttributes
|
||||
* @see ErrorProperties
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("${server.error.path:${error.path:/error}}")
|
||||
public class BasicErrorController extends AbstractErrorController {
|
||||
|
||||
private final ErrorProperties errorProperties;
|
||||
|
||||
/**
|
||||
* Create a new {@link BasicErrorController} instance.
|
||||
* @param errorAttributes the error attributes
|
||||
* @param errorProperties configuration properties
|
||||
*/
|
||||
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
|
||||
this(errorAttributes, errorProperties, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link BasicErrorController} instance.
|
||||
* @param errorAttributes the error attributes
|
||||
* @param errorProperties configuration properties
|
||||
* @param errorViewResolvers error view resolvers
|
||||
*/
|
||||
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties,
|
||||
List<ErrorViewResolver> errorViewResolvers) {
|
||||
super(errorAttributes, errorViewResolvers);
|
||||
Assert.notNull(errorProperties, "'errorProperties' must not be null");
|
||||
this.errorProperties = errorProperties;
|
||||
}
|
||||
|
||||
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
|
||||
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
|
||||
HttpStatus status = getStatus(request);
|
||||
Map<String, Object> model = Collections
|
||||
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
|
||||
response.setStatus(status.value());
|
||||
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
|
||||
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
|
||||
}
|
||||
|
||||
@RequestMapping
|
||||
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
|
||||
HttpStatus status = getStatus(request);
|
||||
if (status == HttpStatus.NO_CONTENT) {
|
||||
return new ResponseEntity<>(status);
|
||||
}
|
||||
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
|
||||
return new ResponseEntity<>(body, status);
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
|
||||
public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {
|
||||
HttpStatus status = getStatus(request);
|
||||
return ResponseEntity.status(status).build();
|
||||
}
|
||||
|
||||
protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) {
|
||||
ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
|
||||
if (this.errorProperties.isIncludeException()) {
|
||||
options = options.including(Include.EXCEPTION);
|
||||
}
|
||||
if (isIncludeStackTrace(request, mediaType)) {
|
||||
options = options.including(Include.STACK_TRACE);
|
||||
}
|
||||
if (isIncludeMessage(request, mediaType)) {
|
||||
options = options.including(Include.MESSAGE);
|
||||
}
|
||||
if (isIncludeBindingErrors(request, mediaType)) {
|
||||
options = options.including(Include.BINDING_ERRORS);
|
||||
}
|
||||
options = isIncludePath(request, mediaType) ? options.including(Include.PATH) : options.excluding(Include.PATH);
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the stacktrace attribute should be included.
|
||||
* @param request the source request
|
||||
* @param produces the media type produced (or {@code MediaType.ALL})
|
||||
* @return if the stacktrace attribute should be included
|
||||
*/
|
||||
protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
|
||||
return switch (getErrorProperties().getIncludeStacktrace()) {
|
||||
case ALWAYS -> true;
|
||||
case ON_PARAM -> getTraceParameter(request);
|
||||
case NEVER -> false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the message attribute should be included.
|
||||
* @param request the source request
|
||||
* @param produces the media type produced (or {@code MediaType.ALL})
|
||||
* @return if the message attribute should be included
|
||||
*/
|
||||
protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) {
|
||||
return switch (getErrorProperties().getIncludeMessage()) {
|
||||
case ALWAYS -> true;
|
||||
case ON_PARAM -> getMessageParameter(request);
|
||||
case NEVER -> false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the errors attribute should be included.
|
||||
* @param request the source request
|
||||
* @param produces the media type produced (or {@code MediaType.ALL})
|
||||
* @return if the errors attribute should be included
|
||||
*/
|
||||
protected boolean isIncludeBindingErrors(HttpServletRequest request, MediaType produces) {
|
||||
return switch (getErrorProperties().getIncludeBindingErrors()) {
|
||||
case ALWAYS -> true;
|
||||
case ON_PARAM -> getErrorsParameter(request);
|
||||
case NEVER -> false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the path attribute should be included.
|
||||
* @param request the source request
|
||||
* @param produces the media type produced (or {@code MediaType.ALL})
|
||||
* @return if the path attribute should be included
|
||||
*/
|
||||
protected boolean isIncludePath(HttpServletRequest request, MediaType produces) {
|
||||
return switch (getErrorProperties().getIncludePath()) {
|
||||
case ALWAYS -> true;
|
||||
case ON_PARAM -> getPathParameter(request);
|
||||
case NEVER -> false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to the error properties.
|
||||
* @return the error properties
|
||||
*/
|
||||
protected ErrorProperties getErrorProperties() {
|
||||
return this.errorProperties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure.error;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatus.Series;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.View;
|
||||
|
||||
/**
|
||||
* Default {@link ErrorViewResolver} implementation that attempts to resolve error views
|
||||
* using well known conventions. Will search for templates and static assets under
|
||||
* {@code '/error'} using the {@link HttpStatus status code} and the
|
||||
* {@link HttpStatus#series() status series}.
|
||||
* <p>
|
||||
* For example, an {@code HTTP 404} will search (in the specific order):
|
||||
* <ul>
|
||||
* <li>{@code '/<templates>/error/404.<ext>'}</li>
|
||||
* <li>{@code '/<static>/error/404.html'}</li>
|
||||
* <li>{@code '/<templates>/error/4xx.<ext>'}</li>
|
||||
* <li>{@code '/<static>/error/4xx.html'}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
|
||||
|
||||
private static final Map<Series, String> SERIES_VIEWS;
|
||||
|
||||
static {
|
||||
Map<Series, String> views = new EnumMap<>(Series.class);
|
||||
views.put(Series.CLIENT_ERROR, "4xx");
|
||||
views.put(Series.SERVER_ERROR, "5xx");
|
||||
SERIES_VIEWS = Collections.unmodifiableMap(views);
|
||||
}
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final Resources resources;
|
||||
|
||||
private final TemplateAvailabilityProviders templateAvailabilityProviders;
|
||||
|
||||
private int order = Ordered.LOWEST_PRECEDENCE;
|
||||
|
||||
/**
|
||||
* Create a new {@link DefaultErrorViewResolver} instance.
|
||||
* @param applicationContext the source application context
|
||||
* @param resources resource properties
|
||||
*/
|
||||
public DefaultErrorViewResolver(ApplicationContext applicationContext, Resources resources) {
|
||||
Assert.notNull(applicationContext, "'applicationContext' must not be null");
|
||||
Assert.notNull(resources, "'resources' must not be null");
|
||||
this.applicationContext = applicationContext;
|
||||
this.resources = resources;
|
||||
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
|
||||
}
|
||||
|
||||
DefaultErrorViewResolver(ApplicationContext applicationContext, Resources resourceProperties,
|
||||
TemplateAvailabilityProviders templateAvailabilityProviders) {
|
||||
Assert.notNull(applicationContext, "ApplicationContext must not be null");
|
||||
Assert.notNull(resourceProperties, "Resources must not be null");
|
||||
this.applicationContext = applicationContext;
|
||||
this.resources = resourceProperties;
|
||||
this.templateAvailabilityProviders = templateAvailabilityProviders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
|
||||
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
|
||||
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
|
||||
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
|
||||
}
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
private ModelAndView resolve(String viewName, Map<String, Object> model) {
|
||||
String errorViewName = "error/" + viewName;
|
||||
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
|
||||
this.applicationContext);
|
||||
if (provider != null) {
|
||||
return new ModelAndView(errorViewName, model);
|
||||
}
|
||||
return resolveResource(errorViewName, model);
|
||||
}
|
||||
|
||||
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
|
||||
for (String location : this.resources.getStaticLocations()) {
|
||||
try {
|
||||
Resource resource = this.applicationContext.getResource(location);
|
||||
resource = resource.createRelative(viewName + ".html");
|
||||
if (resource.exists()) {
|
||||
return new ModelAndView(new HtmlResourceView(resource), model);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link View} backed by an HTML resource.
|
||||
*/
|
||||
private static class HtmlResourceView implements View {
|
||||
|
||||
private final Resource resource;
|
||||
|
||||
HtmlResourceView(Resource resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return MediaType.TEXT_HTML_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
response.setContentType(getContentType());
|
||||
FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* 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.webmvc.autoconfigure.error;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.Servlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
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.autoconfigure.condition.SearchStrategy;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.server.ErrorPage;
|
||||
import org.springframework.boot.web.server.ErrorPageRegistrar;
|
||||
import org.springframework.boot.web.server.ErrorPageRegistry;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath;
|
||||
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.webmvc.autoconfigure.WebMvcProperties;
|
||||
import org.springframework.boot.webmvc.error.DefaultErrorAttributes;
|
||||
import org.springframework.boot.webmvc.error.ErrorAttributes;
|
||||
import org.springframework.boot.webmvc.error.ErrorController;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.view.BeanNameViewResolver;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} to render errors through an MVC
|
||||
* error controller.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
* @since 4.0.0
|
||||
*/
|
||||
// Load before the main WebMvcAutoConfiguration so that the error View is available
|
||||
@AutoConfiguration(before = WebMvcAutoConfiguration.class)
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
|
||||
@EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class })
|
||||
public class ErrorMvcAutoConfiguration {
|
||||
|
||||
private final ServerProperties serverProperties;
|
||||
|
||||
public ErrorMvcAutoConfiguration(ServerProperties serverProperties) {
|
||||
this.serverProperties = serverProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
|
||||
public DefaultErrorAttributes errorAttributes() {
|
||||
return new DefaultErrorAttributes();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
|
||||
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
|
||||
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
|
||||
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
|
||||
errorViewResolvers.orderedStream().toList());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
|
||||
return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
|
||||
return new PreserveErrorControllerTargetClassPostProcessor();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties({ WebProperties.class, WebMvcProperties.class })
|
||||
static class DefaultErrorViewResolverConfiguration {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final Resources resources;
|
||||
|
||||
DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, WebProperties webProperties) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.resources = webProperties.getResources();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(DispatcherServlet.class)
|
||||
@ConditionalOnMissingBean(ErrorViewResolver.class)
|
||||
DefaultErrorViewResolver conventionErrorViewResolver() {
|
||||
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBooleanProperty(name = "server.error.whitelabel.enabled", matchIfMissing = true)
|
||||
@Conditional(ErrorTemplateMissingCondition.class)
|
||||
protected static class WhitelabelErrorViewConfiguration {
|
||||
|
||||
private final StaticView defaultErrorView = new StaticView();
|
||||
|
||||
@Bean(name = "error")
|
||||
@ConditionalOnMissingBean(name = "error")
|
||||
public View defaultErrorView() {
|
||||
return this.defaultErrorView;
|
||||
}
|
||||
|
||||
// If the user adds @EnableWebMvc then the bean name view resolver from
|
||||
// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public BeanNameViewResolver beanNameViewResolver() {
|
||||
BeanNameViewResolver resolver = new BeanNameViewResolver();
|
||||
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
|
||||
return resolver;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SpringBootCondition} that matches when no error template view is detected.
|
||||
*/
|
||||
private static final class ErrorTemplateMissingCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
ConditionMessage.Builder message = ConditionMessage.forCondition("ErrorTemplate Missing");
|
||||
TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders(context.getClassLoader());
|
||||
TemplateAvailabilityProvider provider = providers.getProvider("error", context.getEnvironment(),
|
||||
context.getClassLoader(), context.getResourceLoader());
|
||||
if (provider != null) {
|
||||
return ConditionOutcome.noMatch(message.foundExactly("template from " + provider));
|
||||
}
|
||||
return ConditionOutcome.match(message.didNotFind("error template view").atAll());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple {@link View} implementation that writes a default HTML error page.
|
||||
*/
|
||||
private static final class StaticView implements View {
|
||||
|
||||
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
|
||||
|
||||
private static final Log logger = LogFactory.getLog(StaticView.class);
|
||||
|
||||
@Override
|
||||
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
if (response.isCommitted()) {
|
||||
String message = getMessage(model);
|
||||
logger.error(message);
|
||||
return;
|
||||
}
|
||||
response.setContentType(TEXT_HTML_UTF8.toString());
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Object timestamp = model.get("timestamp");
|
||||
Object message = model.get("message");
|
||||
Object trace = model.get("trace");
|
||||
if (response.getContentType() == null) {
|
||||
response.setContentType(getContentType());
|
||||
}
|
||||
builder.append("<html><body><h1>Whitelabel Error Page</h1>")
|
||||
.append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
|
||||
.append("<div id='created'>")
|
||||
.append(timestamp)
|
||||
.append("</div>")
|
||||
.append("<div>There was an unexpected error (type=")
|
||||
.append(htmlEscape(model.get("error")))
|
||||
.append(", status=")
|
||||
.append(htmlEscape(model.get("status")))
|
||||
.append(").</div>");
|
||||
if (message != null) {
|
||||
builder.append("<div>").append(htmlEscape(message)).append("</div>");
|
||||
}
|
||||
if (trace != null) {
|
||||
builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
|
||||
}
|
||||
builder.append("</body></html>");
|
||||
response.getWriter().append(builder.toString());
|
||||
}
|
||||
|
||||
private String htmlEscape(Object input) {
|
||||
return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null;
|
||||
}
|
||||
|
||||
private String getMessage(Map<String, ?> model) {
|
||||
Object path = model.get("path");
|
||||
String message = "Cannot render error page for request [" + path + "]";
|
||||
if (model.get("message") != null) {
|
||||
message += " and exception [" + model.get("message") + "]";
|
||||
}
|
||||
message += " as the response has already been committed.";
|
||||
message += " As a result, the response may have the wrong status code.";
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return "text/html";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link WebServerFactoryCustomizer} that configures the server's error pages.
|
||||
*/
|
||||
static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
|
||||
|
||||
private final ServerProperties properties;
|
||||
|
||||
private final DispatcherServletPath dispatcherServletPath;
|
||||
|
||||
protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
|
||||
this.properties = properties;
|
||||
this.dispatcherServletPath = dispatcherServletPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
|
||||
ErrorPage errorPage = new ErrorPage(
|
||||
this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
|
||||
errorPageRegistry.addErrorPages(errorPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BeanFactoryPostProcessor} to ensure that the target class of ErrorController
|
||||
* MVC beans are preserved when using AOP.
|
||||
*/
|
||||
static class PreserveErrorControllerTargetClassPostProcessor implements BeanFactoryPostProcessor {
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
String[] errorControllerBeans = beanFactory.getBeanNamesForType(ErrorController.class, false, false);
|
||||
for (String errorControllerBean : errorControllerBeans) {
|
||||
try {
|
||||
beanFactory.getBeanDefinition(errorControllerBean)
|
||||
.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.webmvc.autoconfigure.error;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* Interface that can be implemented by beans that resolve error views.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ErrorViewResolver {
|
||||
|
||||
/**
|
||||
* Resolve an error view for the specified details.
|
||||
* @param request the source request
|
||||
* @param status the http status of the error
|
||||
* @param model the suggested model to be used with the view
|
||||
* @return a resolved {@link ModelAndView} or {@code null}
|
||||
*/
|
||||
ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model);
|
||||
|
||||
}
|
||||
@@ -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 Spring MVC error handling.
|
||||
*/
|
||||
package org.springframework.boot.webmvc.autoconfigure.error;
|
||||
@@ -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 Spring MVC.
|
||||
*/
|
||||
package org.springframework.boot.webmvc.autoconfigure;
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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.webmvc.error;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.web.error.Error;
|
||||
import org.springframework.boot.web.error.ErrorAttributeOptions;
|
||||
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.method.MethodValidationResult;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ErrorAttributes}. Provides the following attributes
|
||||
* when possible:
|
||||
* <ul>
|
||||
* <li>timestamp - The time that the errors were extracted</li>
|
||||
* <li>status - The status code</li>
|
||||
* <li>error - The error reason</li>
|
||||
* <li>exception - The class name of the root exception (if configured)</li>
|
||||
* <li>message - The exception message (if configured)</li>
|
||||
* <li>errors - Any validation errors wrapped in {@link Error}, derived from a
|
||||
* {@link BindingResult} or {@link MethodValidationResult} exception (if configured)</li>
|
||||
* <li>trace - The exception stack trace (if configured)</li>
|
||||
* <li>path - The URL path when the exception was raised</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @author Vedran Pavic
|
||||
* @author Scott Frederick
|
||||
* @author Moritz Halbritter
|
||||
* @author Yanming Zhou
|
||||
* @author Yongjun Hong
|
||||
* @since 4.0.0
|
||||
* @see ErrorAttributes
|
||||
*/
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
|
||||
|
||||
private static final String ERROR_INTERNAL_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
Exception ex) {
|
||||
storeErrorAttributes(request, ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
|
||||
request.setAttribute(ERROR_INTERNAL_ATTRIBUTE, ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
|
||||
Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
|
||||
options.retainIncluded(errorAttributes);
|
||||
return errorAttributes;
|
||||
}
|
||||
|
||||
private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
|
||||
Map<String, Object> errorAttributes = new LinkedHashMap<>();
|
||||
errorAttributes.put("timestamp", new Date());
|
||||
addStatus(errorAttributes, webRequest);
|
||||
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
|
||||
addPath(errorAttributes, webRequest);
|
||||
return errorAttributes;
|
||||
}
|
||||
|
||||
private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
|
||||
Integer status = getAttribute(requestAttributes, RequestDispatcher.ERROR_STATUS_CODE);
|
||||
if (status == null) {
|
||||
errorAttributes.put("status", 999);
|
||||
errorAttributes.put("error", "None");
|
||||
return;
|
||||
}
|
||||
errorAttributes.put("status", status);
|
||||
try {
|
||||
errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Unable to obtain a reason
|
||||
errorAttributes.put("error", "Http Status " + status);
|
||||
}
|
||||
}
|
||||
|
||||
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,
|
||||
boolean includeStackTrace) {
|
||||
Throwable error = getError(webRequest);
|
||||
if (error != null) {
|
||||
while (error instanceof ServletException && error.getCause() != null) {
|
||||
error = error.getCause();
|
||||
}
|
||||
errorAttributes.put("exception", error.getClass().getName());
|
||||
if (includeStackTrace) {
|
||||
addStackTrace(errorAttributes, error);
|
||||
}
|
||||
}
|
||||
addErrorMessage(errorAttributes, webRequest, error);
|
||||
}
|
||||
|
||||
private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {
|
||||
BindingResult bindingResult = extractBindingResult(error);
|
||||
if (bindingResult != null) {
|
||||
addMessageAndErrorsFromBindingResult(errorAttributes, bindingResult);
|
||||
return;
|
||||
}
|
||||
MethodValidationResult methodValidationResult = extractMethodValidationResult(error);
|
||||
if (methodValidationResult != null) {
|
||||
addMessageAndErrorsFromMethodValidationResult(errorAttributes, methodValidationResult);
|
||||
return;
|
||||
}
|
||||
addExceptionErrorMessage(errorAttributes, webRequest, error);
|
||||
}
|
||||
|
||||
private void addMessageAndErrorsFromBindingResult(Map<String, Object> errorAttributes, BindingResult result) {
|
||||
errorAttributes.put("message", "Validation failed for object='%s'. Error count: %s"
|
||||
.formatted(result.getObjectName(), result.getAllErrors().size()));
|
||||
errorAttributes.put("errors", Error.wrap(result.getAllErrors()));
|
||||
}
|
||||
|
||||
private void addMessageAndErrorsFromMethodValidationResult(Map<String, Object> errorAttributes,
|
||||
MethodValidationResult result) {
|
||||
errorAttributes.put("message", "Validation failed for method='%s'. Error count: %s"
|
||||
.formatted(result.getMethod(), result.getAllErrors().size()));
|
||||
errorAttributes.put("errors", Error.wrap(result.getAllErrors()));
|
||||
}
|
||||
|
||||
private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {
|
||||
errorAttributes.put("message", getMessage(webRequest, error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message to be included as the value of the {@code message} error
|
||||
* attribute. By default the returned message is the first of the following that is
|
||||
* not empty:
|
||||
* <ol>
|
||||
* <li>Value of the {@link RequestDispatcher#ERROR_MESSAGE} request attribute.
|
||||
* <li>Message of the given {@code error}.
|
||||
* <li>{@code No message available}.
|
||||
* </ol>
|
||||
* @param webRequest current request
|
||||
* @param error current error, if any
|
||||
* @return message to include in the error attributes
|
||||
*/
|
||||
protected String getMessage(WebRequest webRequest, Throwable error) {
|
||||
Object message = getAttribute(webRequest, RequestDispatcher.ERROR_MESSAGE);
|
||||
if (!ObjectUtils.isEmpty(message)) {
|
||||
return message.toString();
|
||||
}
|
||||
if (error != null && StringUtils.hasLength(error.getMessage())) {
|
||||
return error.getMessage();
|
||||
}
|
||||
return "No message available";
|
||||
}
|
||||
|
||||
private BindingResult extractBindingResult(Throwable error) {
|
||||
if (error instanceof BindingResult bindingResult) {
|
||||
return bindingResult;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private MethodValidationResult extractMethodValidationResult(Throwable error) {
|
||||
if (error instanceof MethodValidationResult methodValidationResult) {
|
||||
return methodValidationResult;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
|
||||
StringWriter stackTrace = new StringWriter();
|
||||
error.printStackTrace(new PrintWriter(stackTrace));
|
||||
stackTrace.flush();
|
||||
errorAttributes.put("trace", stackTrace.toString());
|
||||
}
|
||||
|
||||
private void addPath(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
|
||||
String path = getAttribute(requestAttributes, RequestDispatcher.ERROR_REQUEST_URI);
|
||||
if (path != null) {
|
||||
errorAttributes.put("path", path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable getError(WebRequest webRequest) {
|
||||
Throwable exception = getAttribute(webRequest, ERROR_INTERNAL_ATTRIBUTE);
|
||||
if (exception == null) {
|
||||
exception = getAttribute(webRequest, RequestDispatcher.ERROR_EXCEPTION);
|
||||
}
|
||||
return exception;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
|
||||
return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.webmvc.error;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.web.error.ErrorAttributeOptions;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* Provides access to error attributes which can be logged or presented to the user.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @since 4.0.0
|
||||
* @see DefaultErrorAttributes
|
||||
*/
|
||||
public interface ErrorAttributes {
|
||||
|
||||
/**
|
||||
* Returns a {@link Map} of the error attributes. The map can be used as the model of
|
||||
* an error page {@link ModelAndView}, or returned as a
|
||||
* {@link ResponseBody @ResponseBody}.
|
||||
* @param webRequest the source request
|
||||
* @param options options for error attribute contents
|
||||
* @return a map of error attributes
|
||||
*/
|
||||
default Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying cause of the error or {@code null} if the error cannot be
|
||||
* extracted.
|
||||
* @param webRequest the source request
|
||||
* @return the {@link Exception} that caused the error or {@code null}
|
||||
*/
|
||||
Throwable getError(WebRequest webRequest);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.webmvc.error;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
/**
|
||||
* Marker interface used to identify a {@link Controller @Controller} that should be used
|
||||
* to render errors.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public interface ErrorController {
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spring MVC error handling infrastructure.
|
||||
*/
|
||||
package org.springframework.boot.webmvc.error;
|
||||
@@ -0,0 +1,156 @@
|
||||
{
|
||||
"groups": [],
|
||||
"properties": [
|
||||
{
|
||||
"name": "spring.mvc.date-format",
|
||||
"type": "java.lang.String",
|
||||
"description": "Date format to use, for example 'dd/MM/yyyy'.",
|
||||
"deprecation": {
|
||||
"level": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.favicon.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to enable resolution of favicon.ico.",
|
||||
"deprecation": {
|
||||
"level": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.formcontent.filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to enable Spring's FormContentFilter.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.formcontent.putfilter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to enable Spring's HttpPutFormContentFilter.",
|
||||
"defaultValue": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.mvc.formcontent.filter.enabled",
|
||||
"level": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.hiddenmethod.filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to enable Spring's HiddenHttpMethodFilter.",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.ignore-default-model-on-redirect",
|
||||
"deprecation": {
|
||||
"reason": "Deprecated for removal in Spring MVC.",
|
||||
"level": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.locale",
|
||||
"type": "java.util.Locale",
|
||||
"deprecation": {
|
||||
"replacement": "spring.web.locale",
|
||||
"level": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.locale-resolver",
|
||||
"type": "org.springframework.boot.autoconfigure.web.WebProperties$LocaleResolver",
|
||||
"deprecation": {
|
||||
"replacement": "spring.web.locale-resolver",
|
||||
"level": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.throw-exception-if-no-handler-found",
|
||||
"deprecation": {
|
||||
"reason": "DispatcherServlet property is deprecated for removal and should no longer need to be configured.",
|
||||
"level": "error"
|
||||
}
|
||||
}
|
||||
],
|
||||
"hints": [
|
||||
{
|
||||
"name": "spring.mvc.converters.preferred-json-mapper",
|
||||
"values": [
|
||||
{
|
||||
"value": "gson"
|
||||
},
|
||||
{
|
||||
"value": "jackson"
|
||||
},
|
||||
{
|
||||
"value": "jsonb"
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
{
|
||||
"name": "any"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.format.date",
|
||||
"values": [
|
||||
{
|
||||
"value": "dd/MM/yyyy",
|
||||
"description": "Example date format. Any format supported by DateTimeFormatter.parse can be used."
|
||||
},
|
||||
{
|
||||
"value": "iso",
|
||||
"description": "ISO-8601 extended local date format."
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
{
|
||||
"name": "any"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.format.date-time",
|
||||
"values": [
|
||||
{
|
||||
"value": "yyyy-MM-dd HH:mm:ss",
|
||||
"description": "Example date-time format. Any format supported by DateTimeFormatter.parse can be used."
|
||||
},
|
||||
{
|
||||
"value": "iso",
|
||||
"description": "ISO-8601 extended local date-time format."
|
||||
},
|
||||
{
|
||||
"value": "iso-offset",
|
||||
"description": "ISO offset date-time format."
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
{
|
||||
"name": "any"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.mvc.format.time",
|
||||
"values": [
|
||||
{
|
||||
"value": "HH:mm:ss",
|
||||
"description": "Example time format. Any format supported by DateTimeFormatter.parse can be used."
|
||||
},
|
||||
{
|
||||
"value": "iso",
|
||||
"description": "ISO-8601 extended local time format."
|
||||
},
|
||||
{
|
||||
"value": "iso-offset",
|
||||
"description": "ISO offset time format."
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
{
|
||||
"name": "any"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Template Availability Providers
|
||||
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
|
||||
org.springframework.boot.webmvc.autoconfigure.JspTemplateAvailabilityProvider
|
||||
@@ -0,0 +1,3 @@
|
||||
org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration
|
||||
org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration
|
||||
org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration
|
||||
Reference in New Issue
Block a user