001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.reactive.config; 018 019import java.util.List; 020import java.util.Map; 021import java.util.function.Predicate; 022 023import reactor.core.publisher.Mono; 024 025import org.springframework.beans.BeanUtils; 026import org.springframework.beans.factory.BeanInitializationException; 027import org.springframework.beans.factory.annotation.Qualifier; 028import org.springframework.context.ApplicationContext; 029import org.springframework.context.ApplicationContextAware; 030import org.springframework.context.annotation.Bean; 031import org.springframework.core.ReactiveAdapterRegistry; 032import org.springframework.core.annotation.Order; 033import org.springframework.core.convert.converter.Converter; 034import org.springframework.core.io.DefaultResourceLoader; 035import org.springframework.core.io.ResourceLoader; 036import org.springframework.format.Formatter; 037import org.springframework.format.FormatterRegistry; 038import org.springframework.format.support.DefaultFormattingConversionService; 039import org.springframework.format.support.FormattingConversionService; 040import org.springframework.http.codec.ServerCodecConfigurer; 041import org.springframework.lang.Nullable; 042import org.springframework.util.Assert; 043import org.springframework.util.ClassUtils; 044import org.springframework.validation.Errors; 045import org.springframework.validation.MessageCodesResolver; 046import org.springframework.validation.Validator; 047import org.springframework.web.bind.WebDataBinder; 048import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; 049import org.springframework.web.cors.CorsConfiguration; 050import org.springframework.web.reactive.DispatcherHandler; 051import org.springframework.web.reactive.HandlerMapping; 052import org.springframework.web.reactive.accept.RequestedContentTypeResolver; 053import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; 054import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter; 055import org.springframework.web.reactive.function.server.support.RouterFunctionMapping; 056import org.springframework.web.reactive.function.server.support.ServerResponseResultHandler; 057import org.springframework.web.reactive.handler.AbstractHandlerMapping; 058import org.springframework.web.reactive.handler.WebFluxResponseStatusExceptionHandler; 059import org.springframework.web.reactive.resource.ResourceUrlProvider; 060import org.springframework.web.reactive.result.SimpleHandlerAdapter; 061import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; 062import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; 063import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; 064import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler; 065import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler; 066import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; 067import org.springframework.web.reactive.result.view.ViewResolver; 068import org.springframework.web.server.ServerWebExchange; 069import org.springframework.web.server.WebExceptionHandler; 070import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; 071import org.springframework.web.server.i18n.LocaleContextResolver; 072 073/** 074 * The main class for Spring WebFlux configuration. 075 * 076 * <p>Import directly or extend and override protected methods to customize. 077 * 078 * @author Rossen Stoyanchev 079 * @author Brian Clozel 080 * @since 5.0 081 */ 082public class WebFluxConfigurationSupport implements ApplicationContextAware { 083 084 @Nullable 085 private Map<String, CorsConfiguration> corsConfigurations; 086 087 @Nullable 088 private PathMatchConfigurer pathMatchConfigurer; 089 090 @Nullable 091 private ViewResolverRegistry viewResolverRegistry; 092 093 @Nullable 094 private ApplicationContext applicationContext; 095 096 097 @Override 098 public void setApplicationContext(@Nullable ApplicationContext applicationContext) { 099 this.applicationContext = applicationContext; 100 if (applicationContext != null) { 101 Assert.state(!applicationContext.containsBean("mvcContentNegotiationManager"), 102 "The Java/XML config for Spring MVC and Spring WebFlux cannot both be enabled, " + 103 "e.g. via @EnableWebMvc and @EnableWebFlux, in the same application."); 104 } 105 } 106 107 @Nullable 108 public final ApplicationContext getApplicationContext() { 109 return this.applicationContext; 110 } 111 112 113 @Bean 114 public DispatcherHandler webHandler() { 115 return new DispatcherHandler(); 116 } 117 118 @Bean 119 @Order(0) 120 public WebExceptionHandler responseStatusExceptionHandler() { 121 return new WebFluxResponseStatusExceptionHandler(); 122 } 123 124 @Bean 125 public RequestMappingHandlerMapping requestMappingHandlerMapping( 126 @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) { 127 128 RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping(); 129 mapping.setOrder(0); 130 mapping.setContentTypeResolver(contentTypeResolver); 131 mapping.setCorsConfigurations(getCorsConfigurations()); 132 133 PathMatchConfigurer configurer = getPathMatchConfigurer(); 134 Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch(); 135 if (useTrailingSlashMatch != null) { 136 mapping.setUseTrailingSlashMatch(useTrailingSlashMatch); 137 } 138 Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch(); 139 if (useCaseSensitiveMatch != null) { 140 mapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch); 141 } 142 Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes(); 143 if (pathPrefixes != null) { 144 mapping.setPathPrefixes(pathPrefixes); 145 } 146 147 return mapping; 148 } 149 150 /** 151 * Override to plug a sub-class of {@link RequestMappingHandlerMapping}. 152 */ 153 protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { 154 return new RequestMappingHandlerMapping(); 155 } 156 157 @Bean 158 public RequestedContentTypeResolver webFluxContentTypeResolver() { 159 RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder(); 160 configureContentTypeResolver(builder); 161 return builder.build(); 162 } 163 164 /** 165 * Override to configure how the requested content type is resolved. 166 */ 167 protected void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) { 168 } 169 170 /** 171 * Callback for building the global CORS configuration. This method is final. 172 * Use {@link #addCorsMappings(CorsRegistry)} to customize the CORS conifg. 173 */ 174 protected final Map<String, CorsConfiguration> getCorsConfigurations() { 175 if (this.corsConfigurations == null) { 176 CorsRegistry registry = new CorsRegistry(); 177 addCorsMappings(registry); 178 this.corsConfigurations = registry.getCorsConfigurations(); 179 } 180 return this.corsConfigurations; 181 } 182 183 /** 184 * Override this method to configure cross origin requests processing. 185 * @see CorsRegistry 186 */ 187 protected void addCorsMappings(CorsRegistry registry) { 188 } 189 190 /** 191 * Callback for building the {@link PathMatchConfigurer}. This method is 192 * final, use {@link #configurePathMatching} to customize path matching. 193 */ 194 protected final PathMatchConfigurer getPathMatchConfigurer() { 195 if (this.pathMatchConfigurer == null) { 196 this.pathMatchConfigurer = new PathMatchConfigurer(); 197 configurePathMatching(this.pathMatchConfigurer); 198 } 199 return this.pathMatchConfigurer; 200 } 201 202 /** 203 * Override to configure path matching options. 204 */ 205 public void configurePathMatching(PathMatchConfigurer configurer) { 206 } 207 208 @Bean 209 public RouterFunctionMapping routerFunctionMapping(ServerCodecConfigurer serverCodecConfigurer) { 210 RouterFunctionMapping mapping = createRouterFunctionMapping(); 211 mapping.setOrder(-1); // go before RequestMappingHandlerMapping 212 mapping.setMessageReaders(serverCodecConfigurer.getReaders()); 213 mapping.setCorsConfigurations(getCorsConfigurations()); 214 215 return mapping; 216 } 217 218 /** 219 * Override to plug a sub-class of {@link RouterFunctionMapping}. 220 */ 221 protected RouterFunctionMapping createRouterFunctionMapping() { 222 return new RouterFunctionMapping(); 223 } 224 225 /** 226 * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped 227 * resource handlers. To configure resource handling, override 228 * {@link #addResourceHandlers}. 229 */ 230 @Bean 231 public HandlerMapping resourceHandlerMapping(ResourceUrlProvider resourceUrlProvider) { 232 ResourceLoader resourceLoader = this.applicationContext; 233 if (resourceLoader == null) { 234 resourceLoader = new DefaultResourceLoader(); 235 } 236 ResourceHandlerRegistry registry = new ResourceHandlerRegistry(resourceLoader); 237 registry.setResourceUrlProvider(resourceUrlProvider); 238 addResourceHandlers(registry); 239 240 AbstractHandlerMapping handlerMapping = registry.getHandlerMapping(); 241 if (handlerMapping != null) { 242 PathMatchConfigurer configurer = getPathMatchConfigurer(); 243 Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch(); 244 Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch(); 245 if (useTrailingSlashMatch != null) { 246 handlerMapping.setUseTrailingSlashMatch(useTrailingSlashMatch); 247 } 248 if (useCaseSensitiveMatch != null) { 249 handlerMapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch); 250 } 251 } 252 else { 253 handlerMapping = new EmptyHandlerMapping(); 254 } 255 return handlerMapping; 256 } 257 258 @Bean 259 public ResourceUrlProvider resourceUrlProvider() { 260 return new ResourceUrlProvider(); 261 } 262 263 /** 264 * Override this method to add resource handlers for serving static resources. 265 * @see ResourceHandlerRegistry 266 */ 267 protected void addResourceHandlers(ResourceHandlerRegistry registry) { 268 } 269 270 @Bean 271 public RequestMappingHandlerAdapter requestMappingHandlerAdapter( 272 @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry, 273 ServerCodecConfigurer serverCodecConfigurer, 274 @Qualifier("webFluxConversionService") FormattingConversionService conversionService, 275 @Qualifier("webFluxValidator") Validator validator) { 276 277 RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); 278 adapter.setMessageReaders(serverCodecConfigurer.getReaders()); 279 adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator)); 280 adapter.setReactiveAdapterRegistry(reactiveAdapterRegistry); 281 282 ArgumentResolverConfigurer configurer = new ArgumentResolverConfigurer(); 283 configureArgumentResolvers(configurer); 284 adapter.setArgumentResolverConfigurer(configurer); 285 286 return adapter; 287 } 288 289 /** 290 * Override to plug a sub-class of {@link RequestMappingHandlerAdapter}. 291 */ 292 protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() { 293 return new RequestMappingHandlerAdapter(); 294 } 295 296 /** 297 * Configure resolvers for custom controller method arguments. 298 */ 299 protected void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { 300 } 301 302 /** 303 * Return the configurer for HTTP message readers and writers. 304 * <p>Use {@link #configureHttpMessageCodecs(ServerCodecConfigurer)} to 305 * configure the readers and writers. 306 */ 307 @Bean 308 public ServerCodecConfigurer serverCodecConfigurer() { 309 ServerCodecConfigurer serverCodecConfigurer = ServerCodecConfigurer.create(); 310 configureHttpMessageCodecs(serverCodecConfigurer); 311 return serverCodecConfigurer; 312 } 313 314 /** 315 * Override to plug a sub-class of {@link LocaleContextResolver}. 316 */ 317 protected LocaleContextResolver createLocaleContextResolver() { 318 return new AcceptHeaderLocaleContextResolver(); 319 } 320 321 @Bean 322 public LocaleContextResolver localeContextResolver() { 323 return createLocaleContextResolver(); 324 } 325 326 /** 327 * Override to configure the HTTP message readers and writers to use. 328 */ 329 protected void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { 330 } 331 332 /** 333 * Return the {@link ConfigurableWebBindingInitializer} to use for 334 * initializing all {@link WebDataBinder} instances. 335 */ 336 protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer( 337 FormattingConversionService webFluxConversionService, Validator webFluxValidator) { 338 339 ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer(); 340 initializer.setConversionService(webFluxConversionService); 341 initializer.setValidator(webFluxValidator); 342 MessageCodesResolver messageCodesResolver = getMessageCodesResolver(); 343 if (messageCodesResolver != null) { 344 initializer.setMessageCodesResolver(messageCodesResolver); 345 } 346 return initializer; 347 } 348 349 /** 350 * Return a {@link FormattingConversionService} for use with annotated controllers. 351 * <p>See {@link #addFormatters} as an alternative to overriding this method. 352 */ 353 @Bean 354 public FormattingConversionService webFluxConversionService() { 355 FormattingConversionService service = new DefaultFormattingConversionService(); 356 addFormatters(service); 357 return service; 358 } 359 360 /** 361 * Override this method to add custom {@link Converter} and/or {@link Formatter} 362 * delegates to the common {@link FormattingConversionService}. 363 * @see #webFluxConversionService() 364 */ 365 protected void addFormatters(FormatterRegistry registry) { 366 } 367 368 /** 369 * Return a {@link ReactiveAdapterRegistry} to adapting reactive types. 370 */ 371 @Bean 372 public ReactiveAdapterRegistry webFluxAdapterRegistry() { 373 return new ReactiveAdapterRegistry(); 374 } 375 376 /** 377 * Return a global {@link Validator} instance for example for validating 378 * {@code @RequestBody} method arguments. 379 * <p>Delegates to {@link #getValidator()} first. If that returns {@code null} 380 * checks the classpath for the presence of a JSR-303 implementations 381 * before creating a {@code OptionalValidatorFactoryBean}. If a JSR-303 382 * implementation is not available, a "no-op" {@link Validator} is returned. 383 */ 384 @Bean 385 public Validator webFluxValidator() { 386 Validator validator = getValidator(); 387 if (validator == null) { 388 if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { 389 Class<?> clazz; 390 try { 391 String name = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean"; 392 clazz = ClassUtils.forName(name, getClass().getClassLoader()); 393 } 394 catch (ClassNotFoundException | LinkageError ex) { 395 throw new BeanInitializationException("Failed to resolve default validator class", ex); 396 } 397 validator = (Validator) BeanUtils.instantiateClass(clazz); 398 } 399 else { 400 validator = new NoOpValidator(); 401 } 402 } 403 return validator; 404 } 405 406 /** 407 * Override this method to provide a custom {@link Validator}. 408 */ 409 @Nullable 410 protected Validator getValidator() { 411 return null; 412 } 413 414 /** 415 * Override this method to provide a custom {@link MessageCodesResolver}. 416 */ 417 @Nullable 418 protected MessageCodesResolver getMessageCodesResolver() { 419 return null; 420 } 421 422 @Bean 423 public HandlerFunctionAdapter handlerFunctionAdapter() { 424 return new HandlerFunctionAdapter(); 425 } 426 427 @Bean 428 public SimpleHandlerAdapter simpleHandlerAdapter() { 429 return new SimpleHandlerAdapter(); 430 } 431 432 @Bean 433 public ResponseEntityResultHandler responseEntityResultHandler( 434 @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry, 435 ServerCodecConfigurer serverCodecConfigurer, 436 @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) { 437 438 return new ResponseEntityResultHandler(serverCodecConfigurer.getWriters(), 439 contentTypeResolver, reactiveAdapterRegistry); 440 } 441 442 @Bean 443 public ResponseBodyResultHandler responseBodyResultHandler( 444 @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry, 445 ServerCodecConfigurer serverCodecConfigurer, 446 @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) { 447 448 return new ResponseBodyResultHandler(serverCodecConfigurer.getWriters(), 449 contentTypeResolver, reactiveAdapterRegistry); 450 } 451 452 @Bean 453 public ViewResolutionResultHandler viewResolutionResultHandler( 454 @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry, 455 @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) { 456 457 ViewResolverRegistry registry = getViewResolverRegistry(); 458 List<ViewResolver> resolvers = registry.getViewResolvers(); 459 ViewResolutionResultHandler handler = new ViewResolutionResultHandler( 460 resolvers, contentTypeResolver, reactiveAdapterRegistry); 461 handler.setDefaultViews(registry.getDefaultViews()); 462 handler.setOrder(registry.getOrder()); 463 return handler; 464 } 465 466 @Bean 467 public ServerResponseResultHandler serverResponseResultHandler(ServerCodecConfigurer serverCodecConfigurer) { 468 List<ViewResolver> resolvers = getViewResolverRegistry().getViewResolvers(); 469 ServerResponseResultHandler handler = new ServerResponseResultHandler(); 470 handler.setMessageWriters(serverCodecConfigurer.getWriters()); 471 handler.setViewResolvers(resolvers); 472 return handler; 473 } 474 475 /** 476 * Callback for building the {@link ViewResolverRegistry}. This method is final, 477 * use {@link #configureViewResolvers} to customize view resolvers. 478 */ 479 protected final ViewResolverRegistry getViewResolverRegistry() { 480 if (this.viewResolverRegistry == null) { 481 this.viewResolverRegistry = new ViewResolverRegistry(this.applicationContext); 482 configureViewResolvers(this.viewResolverRegistry); 483 } 484 return this.viewResolverRegistry; 485 } 486 487 /** 488 * Configure view resolution for supporting template engines. 489 * @see ViewResolverRegistry 490 */ 491 protected void configureViewResolvers(ViewResolverRegistry registry) { 492 } 493 494 495 private static final class EmptyHandlerMapping extends AbstractHandlerMapping { 496 497 @Override 498 public Mono<Object> getHandlerInternal(ServerWebExchange exchange) { 499 return Mono.empty(); 500 } 501 } 502 503 504 private static final class NoOpValidator implements Validator { 505 506 @Override 507 public boolean supports(Class<?> clazz) { 508 return false; 509 } 510 511 @Override 512 public void validate(@Nullable Object target, Errors errors) { 513 } 514 } 515 516}