org.springframework.web.reactive.config.WebFluxConfigurationSupport Maven / Gradle / Ivy
/*
* Copyright 2002-2018 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
*
* http://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.web.reactive.config;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import reactor.core.publisher.Mono;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter;
import org.springframework.web.reactive.function.server.support.RouterFunctionMapping;
import org.springframework.web.reactive.function.server.support.ServerResponseResultHandler;
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.reactive.handler.WebFluxResponseStatusExceptionHandler;
import org.springframework.web.reactive.resource.ResourceUrlProvider;
import org.springframework.web.reactive.result.SimpleHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler;
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.i18n.LocaleContextResolver;
/**
* The main class for Spring WebFlux configuration.
*
* Import directly or extend and override protected methods to customize.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 5.0
*/
public class WebFluxConfigurationSupport implements ApplicationContextAware {
@Nullable
private Map corsConfigurations;
@Nullable
private PathMatchConfigurer pathMatchConfigurer;
@Nullable
private ViewResolverRegistry viewResolverRegistry;
@Nullable
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
if (applicationContext != null) {
Assert.state(!applicationContext.containsBean("mvcContentNegotiationManager"),
"The Java/XML config for Spring MVC and Spring WebFlux cannot both be enabled, " +
"e.g. via @EnableWebMvc and @EnableWebFlux, in the same application.");
}
}
@Nullable
public final ApplicationContext getApplicationContext() {
return this.applicationContext;
}
@Bean
public DispatcherHandler webHandler() {
return new DispatcherHandler();
}
@Bean
@Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {
return new WebFluxResponseStatusExceptionHandler();
}
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setContentTypeResolver(webFluxContentTypeResolver());
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch();
if (useCaseSensitiveMatch != null) {
mapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch);
}
Map>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
return mapping;
}
/**
* Override to plug a sub-class of {@link RequestMappingHandlerMapping}.
*/
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
@Bean
public RequestedContentTypeResolver webFluxContentTypeResolver() {
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
configureContentTypeResolver(builder);
return builder.build();
}
/**
* Override to configure how the requested content type is resolved.
*/
protected void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
}
/**
* Callback for building the global CORS configuration. This method is final.
* Use {@link #addCorsMappings(CorsRegistry)} to customize the CORS conifg.
*/
protected final Map getCorsConfigurations() {
if (this.corsConfigurations == null) {
CorsRegistry registry = new CorsRegistry();
addCorsMappings(registry);
this.corsConfigurations = registry.getCorsConfigurations();
}
return this.corsConfigurations;
}
/**
* Override this method to configure cross origin requests processing.
* @see CorsRegistry
*/
protected void addCorsMappings(CorsRegistry registry) {
}
/**
* Callback for building the {@link PathMatchConfigurer}. This method is
* final, use {@link #configurePathMatching} to customize path matching.
*/
protected final PathMatchConfigurer getPathMatchConfigurer() {
if (this.pathMatchConfigurer == null) {
this.pathMatchConfigurer = new PathMatchConfigurer();
configurePathMatching(this.pathMatchConfigurer);
}
return this.pathMatchConfigurer;
}
/**
* Override to configure path matching options.
*/
public void configurePathMatching(PathMatchConfigurer configurer) {
}
@Bean
public RouterFunctionMapping routerFunctionMapping() {
RouterFunctionMapping mapping = createRouterFunctionMapping();
mapping.setOrder(-1); // go before RequestMappingHandlerMapping
mapping.setMessageReaders(serverCodecConfigurer().getReaders());
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
/**
* Override to plug a sub-class of {@link RouterFunctionMapping}.
*/
protected RouterFunctionMapping createRouterFunctionMapping() {
return new RouterFunctionMapping();
}
/**
* Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
* resource handlers. To configure resource handling, override
* {@link #addResourceHandlers}.
*/
@Bean
public HandlerMapping resourceHandlerMapping() {
ResourceLoader resourceLoader = this.applicationContext;
if (resourceLoader == null) {
resourceLoader = new DefaultResourceLoader();
}
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(resourceLoader);
registry.setResourceUrlProvider(resourceUrlProvider());
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
if (handlerMapping != null) {
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch();
if (useTrailingSlashMatch != null) {
handlerMapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
if (useCaseSensitiveMatch != null) {
handlerMapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch);
}
}
else {
handlerMapping = new EmptyHandlerMapping();
}
return handlerMapping;
}
@Bean
public ResourceUrlProvider resourceUrlProvider() {
return new ResourceUrlProvider();
}
/**
* Override this method to add resource handlers for serving static resources.
* @see ResourceHandlerRegistry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setMessageReaders(serverCodecConfigurer().getReaders());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
adapter.setReactiveAdapterRegistry(webFluxAdapterRegistry());
ArgumentResolverConfigurer configurer = new ArgumentResolverConfigurer();
configureArgumentResolvers(configurer);
adapter.setArgumentResolverConfigurer(configurer);
return adapter;
}
/**
* Override to plug a sub-class of {@link RequestMappingHandlerAdapter}.
*/
protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
return new RequestMappingHandlerAdapter();
}
/**
* Configure resolvers for custom controller method arguments.
*/
protected void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
}
/**
* Return the configurer for HTTP message readers and writers.
* Use {@link #configureHttpMessageCodecs(ServerCodecConfigurer)} to
* configure the readers and writers.
*/
@Bean
public ServerCodecConfigurer serverCodecConfigurer() {
ServerCodecConfigurer serverCodecConfigurer = ServerCodecConfigurer.create();
configureHttpMessageCodecs(serverCodecConfigurer);
return serverCodecConfigurer;
}
/**
* Override to plug a sub-class of {@link LocaleContextResolver}.
*/
protected LocaleContextResolver createLocaleContextResolver() {
return new AcceptHeaderLocaleContextResolver();
}
@Bean
public LocaleContextResolver localeContextResolver() {
return createLocaleContextResolver();
}
/**
* Override to configure the HTTP message readers and writers to use.
*/
protected void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
}
/**
* Return the {@link ConfigurableWebBindingInitializer} to use for
* initializing all {@link WebDataBinder} instances.
*/
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(webFluxConversionService());
initializer.setValidator(webFluxValidator());
MessageCodesResolver messageCodesResolver = getMessageCodesResolver();
if (messageCodesResolver != null) {
initializer.setMessageCodesResolver(messageCodesResolver);
}
return initializer;
}
/**
* Return a {@link FormattingConversionService} for use with annotated controllers.
*
See {@link #addFormatters} as an alternative to overriding this method.
*/
@Bean
public FormattingConversionService webFluxConversionService() {
FormattingConversionService service = new DefaultFormattingConversionService();
addFormatters(service);
return service;
}
/**
* Override this method to add custom {@link Converter} and/or {@link Formatter}
* delegates to the common {@link FormattingConversionService}.
* @see #webFluxConversionService()
*/
protected void addFormatters(FormatterRegistry registry) {
}
/**
* Return a {@link ReactiveAdapterRegistry} to adapting reactive types.
*/
@Bean
public ReactiveAdapterRegistry webFluxAdapterRegistry() {
return new ReactiveAdapterRegistry();
}
/**
* Return a global {@link Validator} instance for example for validating
* {@code @RequestBody} method arguments.
*
Delegates to {@link #getValidator()} first. If that returns {@code null}
* checks the classpath for the presence of a JSR-303 implementations
* before creating a {@code OptionalValidatorFactoryBean}. If a JSR-303
* implementation is not available, a "no-op" {@link Validator} is returned.
*/
@Bean
public Validator webFluxValidator() {
Validator validator = getValidator();
if (validator == null) {
if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
Class clazz;
try {
String name = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean";
clazz = ClassUtils.forName(name, getClass().getClassLoader());
}
catch (ClassNotFoundException | LinkageError ex) {
throw new BeanInitializationException("Failed to resolve default validator class", ex);
}
validator = (Validator) BeanUtils.instantiateClass(clazz);
}
else {
validator = new NoOpValidator();
}
}
return validator;
}
/**
* Override this method to provide a custom {@link Validator}.
*/
@Nullable
protected Validator getValidator() {
return null;
}
/**
* Override this method to provide a custom {@link MessageCodesResolver}.
*/
@Nullable
protected MessageCodesResolver getMessageCodesResolver() {
return null;
}
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return new HandlerFunctionAdapter();
}
@Bean
public SimpleHandlerAdapter simpleHandlerAdapter() {
return new SimpleHandlerAdapter();
}
@Bean
public ResponseEntityResultHandler responseEntityResultHandler() {
return new ResponseEntityResultHandler(serverCodecConfigurer().getWriters(),
webFluxContentTypeResolver(), webFluxAdapterRegistry());
}
@Bean
public ResponseBodyResultHandler responseBodyResultHandler() {
return new ResponseBodyResultHandler(serverCodecConfigurer().getWriters(),
webFluxContentTypeResolver(), webFluxAdapterRegistry());
}
@Bean
public ViewResolutionResultHandler viewResolutionResultHandler() {
ViewResolverRegistry registry = getViewResolverRegistry();
List resolvers = registry.getViewResolvers();
ViewResolutionResultHandler handler = new ViewResolutionResultHandler(
resolvers, webFluxContentTypeResolver(), webFluxAdapterRegistry());
handler.setDefaultViews(registry.getDefaultViews());
handler.setOrder(registry.getOrder());
return handler;
}
@Bean
public ServerResponseResultHandler serverResponseResultHandler() {
List resolvers = getViewResolverRegistry().getViewResolvers();
ServerResponseResultHandler handler = new ServerResponseResultHandler();
handler.setMessageWriters(serverCodecConfigurer().getWriters());
handler.setViewResolvers(resolvers);
return handler;
}
/**
* Callback for building the {@link ViewResolverRegistry}. This method is final,
* use {@link #configureViewResolvers} to customize view resolvers.
*/
protected final ViewResolverRegistry getViewResolverRegistry() {
if (this.viewResolverRegistry == null) {
this.viewResolverRegistry = new ViewResolverRegistry(this.applicationContext);
configureViewResolvers(this.viewResolverRegistry);
}
return this.viewResolverRegistry;
}
/**
* Configure view resolution for supporting template engines.
* @see ViewResolverRegistry
*/
protected void configureViewResolvers(ViewResolverRegistry registry) {
}
private static final class EmptyHandlerMapping extends AbstractHandlerMapping {
@Override
public Mono