001/* 002 * Copyright 2012-2017 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 * http://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.boot.autoconfigure.web; 018 019import java.util.Collection; 020import java.util.Collections; 021import java.util.Date; 022import java.util.List; 023import java.util.ListIterator; 024import java.util.Map; 025import java.util.Map.Entry; 026 027import javax.servlet.Servlet; 028import javax.servlet.http.HttpServletRequest; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032 033import org.springframework.beans.factory.BeanFactory; 034import org.springframework.beans.factory.ListableBeanFactory; 035import org.springframework.beans.factory.NoSuchBeanDefinitionException; 036import org.springframework.beans.factory.ObjectProvider; 037import org.springframework.beans.factory.annotation.Autowired; 038import org.springframework.boot.autoconfigure.AutoConfigureAfter; 039import org.springframework.boot.autoconfigure.AutoConfigureOrder; 040import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 041import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 042import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 043import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 044import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 045import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 046import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; 047import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy; 048import org.springframework.boot.context.properties.EnableConfigurationProperties; 049import org.springframework.boot.web.filter.OrderedHiddenHttpMethodFilter; 050import org.springframework.boot.web.filter.OrderedHttpPutFormContentFilter; 051import org.springframework.boot.web.filter.OrderedRequestContextFilter; 052import org.springframework.context.annotation.Bean; 053import org.springframework.context.annotation.Configuration; 054import org.springframework.context.annotation.Import; 055import org.springframework.context.annotation.Lazy; 056import org.springframework.context.annotation.Primary; 057import org.springframework.core.Ordered; 058import org.springframework.core.convert.converter.Converter; 059import org.springframework.core.convert.converter.GenericConverter; 060import org.springframework.core.io.Resource; 061import org.springframework.format.Formatter; 062import org.springframework.format.FormatterRegistry; 063import org.springframework.format.datetime.DateFormatter; 064import org.springframework.http.HttpHeaders; 065import org.springframework.http.MediaType; 066import org.springframework.http.converter.HttpMessageConverter; 067import org.springframework.util.ClassUtils; 068import org.springframework.util.StringUtils; 069import org.springframework.validation.DefaultMessageCodesResolver; 070import org.springframework.validation.MessageCodesResolver; 071import org.springframework.validation.Validator; 072import org.springframework.web.HttpMediaTypeNotAcceptableException; 073import org.springframework.web.accept.ContentNegotiationManager; 074import org.springframework.web.accept.ContentNegotiationStrategy; 075import org.springframework.web.accept.PathExtensionContentNegotiationStrategy; 076import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; 077import org.springframework.web.context.request.NativeWebRequest; 078import org.springframework.web.context.request.RequestAttributes; 079import org.springframework.web.context.request.RequestContextListener; 080import org.springframework.web.filter.HiddenHttpMethodFilter; 081import org.springframework.web.filter.HttpPutFormContentFilter; 082import org.springframework.web.filter.RequestContextFilter; 083import org.springframework.web.servlet.DispatcherServlet; 084import org.springframework.web.servlet.HandlerExceptionResolver; 085import org.springframework.web.servlet.LocaleResolver; 086import org.springframework.web.servlet.View; 087import org.springframework.web.servlet.ViewResolver; 088import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; 089import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; 090import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration; 091import org.springframework.web.servlet.config.annotation.EnableWebMvc; 092import org.springframework.web.servlet.config.annotation.ResourceChainRegistration; 093import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration; 094import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 095import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; 096import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 097import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; 098import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; 099import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; 100import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; 101import org.springframework.web.servlet.i18n.FixedLocaleResolver; 102import org.springframework.web.servlet.mvc.ParameterizableViewController; 103import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; 104import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; 105import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 106import org.springframework.web.servlet.resource.AppCacheManifestTransformer; 107import org.springframework.web.servlet.resource.GzipResourceResolver; 108import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; 109import org.springframework.web.servlet.resource.ResourceResolver; 110import org.springframework.web.servlet.resource.VersionResourceResolver; 111import org.springframework.web.servlet.view.BeanNameViewResolver; 112import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; 113import org.springframework.web.servlet.view.InternalResourceViewResolver; 114 115/** 116 * {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebMvc Web MVC}. 117 * 118 * @author Phillip Webb 119 * @author Dave Syer 120 * @author Andy Wilkinson 121 * @author Sébastien Deleuze 122 * @author Eddú Meléndez 123 * @author Stephane Nicoll 124 */ 125@Configuration 126@ConditionalOnWebApplication 127@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, 128 WebMvcConfigurerAdapter.class }) 129@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 130@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) 131@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, 132 ValidationAutoConfiguration.class }) 133public class WebMvcAutoConfiguration { 134 135 public static final String DEFAULT_PREFIX = ""; 136 137 public static final String DEFAULT_SUFFIX = ""; 138 139 @Bean 140 @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) 141 public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { 142 return new OrderedHiddenHttpMethodFilter(); 143 } 144 145 @Bean 146 @ConditionalOnMissingBean(HttpPutFormContentFilter.class) 147 @ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true) 148 public OrderedHttpPutFormContentFilter httpPutFormContentFilter() { 149 return new OrderedHttpPutFormContentFilter(); 150 } 151 152 // Defined as a nested config to ensure WebMvcConfigurerAdapter is not read when not 153 // on the classpath 154 @Configuration 155 @Import(EnableWebMvcConfiguration.class) 156 @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) 157 public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter { 158 159 private static final Log logger = LogFactory 160 .getLog(WebMvcConfigurerAdapter.class); 161 162 private final ResourceProperties resourceProperties; 163 164 private final WebMvcProperties mvcProperties; 165 166 private final ListableBeanFactory beanFactory; 167 168 private final HttpMessageConverters messageConverters; 169 170 final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; 171 172 public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, 173 WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, 174 @Lazy HttpMessageConverters messageConverters, 175 ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) { 176 this.resourceProperties = resourceProperties; 177 this.mvcProperties = mvcProperties; 178 this.beanFactory = beanFactory; 179 this.messageConverters = messageConverters; 180 this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider 181 .getIfAvailable(); 182 } 183 184 @Override 185 public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { 186 converters.addAll(this.messageConverters.getConverters()); 187 } 188 189 @Override 190 public void configureAsyncSupport(AsyncSupportConfigurer configurer) { 191 Long timeout = this.mvcProperties.getAsync().getRequestTimeout(); 192 if (timeout != null) { 193 configurer.setDefaultTimeout(timeout); 194 } 195 } 196 197 @Override 198 public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { 199 Map<String, MediaType> mediaTypes = this.mvcProperties.getMediaTypes(); 200 for (Entry<String, MediaType> mediaType : mediaTypes.entrySet()) { 201 configurer.mediaType(mediaType.getKey(), mediaType.getValue()); 202 } 203 } 204 205 @Bean 206 @ConditionalOnMissingBean 207 public InternalResourceViewResolver defaultViewResolver() { 208 InternalResourceViewResolver resolver = new InternalResourceViewResolver(); 209 resolver.setPrefix(this.mvcProperties.getView().getPrefix()); 210 resolver.setSuffix(this.mvcProperties.getView().getSuffix()); 211 return resolver; 212 } 213 214 @Bean 215 @ConditionalOnBean(View.class) 216 @ConditionalOnMissingBean 217 public BeanNameViewResolver beanNameViewResolver() { 218 BeanNameViewResolver resolver = new BeanNameViewResolver(); 219 resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10); 220 return resolver; 221 } 222 223 @Bean 224 @ConditionalOnBean(ViewResolver.class) 225 @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class) 226 public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) { 227 ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); 228 resolver.setContentNegotiationManager( 229 beanFactory.getBean(ContentNegotiationManager.class)); 230 // ContentNegotiatingViewResolver uses all the other view resolvers to locate 231 // a view so it should have a high precedence 232 resolver.setOrder(Ordered.HIGHEST_PRECEDENCE); 233 return resolver; 234 } 235 236 @Bean 237 @ConditionalOnMissingBean 238 @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") 239 public LocaleResolver localeResolver() { 240 if (this.mvcProperties 241 .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { 242 return new FixedLocaleResolver(this.mvcProperties.getLocale()); 243 } 244 AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); 245 localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); 246 return localeResolver; 247 } 248 249 @Bean 250 @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format") 251 public Formatter<Date> dateFormatter() { 252 return new DateFormatter(this.mvcProperties.getDateFormat()); 253 } 254 255 @Override 256 public MessageCodesResolver getMessageCodesResolver() { 257 if (this.mvcProperties.getMessageCodesResolverFormat() != null) { 258 DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver(); 259 resolver.setMessageCodeFormatter( 260 this.mvcProperties.getMessageCodesResolverFormat()); 261 return resolver; 262 } 263 return null; 264 } 265 266 @Override 267 public void addFormatters(FormatterRegistry registry) { 268 for (Converter<?, ?> converter : getBeansOfType(Converter.class)) { 269 registry.addConverter(converter); 270 } 271 for (GenericConverter converter : getBeansOfType(GenericConverter.class)) { 272 registry.addConverter(converter); 273 } 274 for (Formatter<?> formatter : getBeansOfType(Formatter.class)) { 275 registry.addFormatter(formatter); 276 } 277 } 278 279 private <T> Collection<T> getBeansOfType(Class<T> type) { 280 return this.beanFactory.getBeansOfType(type).values(); 281 } 282 283 @Override 284 public void addResourceHandlers(ResourceHandlerRegistry registry) { 285 if (!this.resourceProperties.isAddMappings()) { 286 logger.debug("Default resource handling disabled"); 287 return; 288 } 289 Integer cachePeriod = this.resourceProperties.getCachePeriod(); 290 if (!registry.hasMappingForPattern("/webjars/**")) { 291 customizeResourceHandlerRegistration( 292 registry.addResourceHandler("/webjars/**") 293 .addResourceLocations( 294 "classpath:/META-INF/resources/webjars/") 295 .setCachePeriod(cachePeriod)); 296 } 297 String staticPathPattern = this.mvcProperties.getStaticPathPattern(); 298 if (!registry.hasMappingForPattern(staticPathPattern)) { 299 customizeResourceHandlerRegistration( 300 registry.addResourceHandler(staticPathPattern) 301 .addResourceLocations( 302 this.resourceProperties.getStaticLocations()) 303 .setCachePeriod(cachePeriod)); 304 } 305 } 306 307 @Bean 308 public WelcomePageHandlerMapping welcomePageHandlerMapping( 309 ResourceProperties resourceProperties) { 310 return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(), 311 this.mvcProperties.getStaticPathPattern()); 312 } 313 314 private void customizeResourceHandlerRegistration( 315 ResourceHandlerRegistration registration) { 316 if (this.resourceHandlerRegistrationCustomizer != null) { 317 this.resourceHandlerRegistrationCustomizer.customize(registration); 318 } 319 320 } 321 322 @Bean 323 @ConditionalOnMissingBean({ RequestContextListener.class, 324 RequestContextFilter.class }) 325 public static RequestContextFilter requestContextFilter() { 326 return new OrderedRequestContextFilter(); 327 } 328 329 @Configuration 330 @ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true) 331 public static class FaviconConfiguration { 332 333 private final ResourceProperties resourceProperties; 334 335 public FaviconConfiguration(ResourceProperties resourceProperties) { 336 this.resourceProperties = resourceProperties; 337 } 338 339 @Bean 340 public SimpleUrlHandlerMapping faviconHandlerMapping() { 341 SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); 342 mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); 343 mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", 344 faviconRequestHandler())); 345 return mapping; 346 } 347 348 @Bean 349 public ResourceHttpRequestHandler faviconRequestHandler() { 350 ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); 351 requestHandler 352 .setLocations(this.resourceProperties.getFaviconLocations()); 353 return requestHandler; 354 } 355 356 } 357 358 } 359 360 /** 361 * Configuration equivalent to {@code @EnableWebMvc}. 362 */ 363 @Configuration 364 public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { 365 366 private final WebMvcProperties mvcProperties; 367 368 private final ListableBeanFactory beanFactory; 369 370 private final WebMvcRegistrations mvcRegistrations; 371 372 public EnableWebMvcConfiguration( 373 ObjectProvider<WebMvcProperties> mvcPropertiesProvider, 374 ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, 375 ListableBeanFactory beanFactory) { 376 this.mvcProperties = mvcPropertiesProvider.getIfAvailable(); 377 this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique(); 378 this.beanFactory = beanFactory; 379 } 380 381 @Bean 382 @Override 383 public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { 384 RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(); 385 adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null ? true 386 : this.mvcProperties.isIgnoreDefaultModelOnRedirect()); 387 return adapter; 388 } 389 390 @Override 391 protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() { 392 if (this.mvcRegistrations != null 393 && this.mvcRegistrations.getRequestMappingHandlerAdapter() != null) { 394 return this.mvcRegistrations.getRequestMappingHandlerAdapter(); 395 } 396 return super.createRequestMappingHandlerAdapter(); 397 } 398 399 @Bean 400 @Primary 401 @Override 402 public RequestMappingHandlerMapping requestMappingHandlerMapping() { 403 // Must be @Primary for MvcUriComponentsBuilder to work 404 return super.requestMappingHandlerMapping(); 405 } 406 407 @Bean 408 @Override 409 public Validator mvcValidator() { 410 if (!ClassUtils.isPresent("javax.validation.Validator", 411 getClass().getClassLoader())) { 412 return super.mvcValidator(); 413 } 414 return WebMvcValidator.get(getApplicationContext(), getValidator()); 415 } 416 417 @Override 418 protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { 419 if (this.mvcRegistrations != null 420 && this.mvcRegistrations.getRequestMappingHandlerMapping() != null) { 421 return this.mvcRegistrations.getRequestMappingHandlerMapping(); 422 } 423 return super.createRequestMappingHandlerMapping(); 424 } 425 426 @Override 427 protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() { 428 try { 429 return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class); 430 } 431 catch (NoSuchBeanDefinitionException ex) { 432 return super.getConfigurableWebBindingInitializer(); 433 } 434 } 435 436 @Override 437 protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() { 438 if (this.mvcRegistrations != null && this.mvcRegistrations 439 .getExceptionHandlerExceptionResolver() != null) { 440 return this.mvcRegistrations.getExceptionHandlerExceptionResolver(); 441 } 442 return super.createExceptionHandlerExceptionResolver(); 443 } 444 445 @Override 446 protected void configureHandlerExceptionResolvers( 447 List<HandlerExceptionResolver> exceptionResolvers) { 448 super.configureHandlerExceptionResolvers(exceptionResolvers); 449 if (exceptionResolvers.isEmpty()) { 450 addDefaultHandlerExceptionResolvers(exceptionResolvers); 451 } 452 if (this.mvcProperties.isLogResolvedException()) { 453 for (HandlerExceptionResolver resolver : exceptionResolvers) { 454 if (resolver instanceof AbstractHandlerExceptionResolver) { 455 ((AbstractHandlerExceptionResolver) resolver) 456 .setWarnLogCategory(resolver.getClass().getName()); 457 } 458 } 459 } 460 } 461 462 @Bean 463 @Override 464 public ContentNegotiationManager mvcContentNegotiationManager() { 465 ContentNegotiationManager manager = super.mvcContentNegotiationManager(); 466 List<ContentNegotiationStrategy> strategies = manager.getStrategies(); 467 ListIterator<ContentNegotiationStrategy> iterator = strategies.listIterator(); 468 while (iterator.hasNext()) { 469 ContentNegotiationStrategy strategy = iterator.next(); 470 if (strategy instanceof PathExtensionContentNegotiationStrategy) { 471 iterator.set(new OptionalPathExtensionContentNegotiationStrategy( 472 strategy)); 473 } 474 } 475 return manager; 476 } 477 478 } 479 480 @Configuration 481 @ConditionalOnEnabledResourceChain 482 static class ResourceChainCustomizerConfiguration { 483 484 @Bean 485 public ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() { 486 return new ResourceChainResourceHandlerRegistrationCustomizer(); 487 } 488 489 } 490 491 interface ResourceHandlerRegistrationCustomizer { 492 493 void customize(ResourceHandlerRegistration registration); 494 495 } 496 497 private static class ResourceChainResourceHandlerRegistrationCustomizer 498 implements ResourceHandlerRegistrationCustomizer { 499 500 @Autowired 501 private ResourceProperties resourceProperties = new ResourceProperties(); 502 503 @Override 504 public void customize(ResourceHandlerRegistration registration) { 505 ResourceProperties.Chain properties = this.resourceProperties.getChain(); 506 configureResourceChain(properties, 507 registration.resourceChain(properties.isCache())); 508 } 509 510 private void configureResourceChain(ResourceProperties.Chain properties, 511 ResourceChainRegistration chain) { 512 Strategy strategy = properties.getStrategy(); 513 if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) { 514 chain.addResolver(getVersionResourceResolver(strategy)); 515 } 516 if (properties.isGzipped()) { 517 chain.addResolver(new GzipResourceResolver()); 518 } 519 if (properties.isHtmlApplicationCache()) { 520 chain.addTransformer(new AppCacheManifestTransformer()); 521 } 522 } 523 524 private ResourceResolver getVersionResourceResolver( 525 ResourceProperties.Strategy properties) { 526 VersionResourceResolver resolver = new VersionResourceResolver(); 527 if (properties.getFixed().isEnabled()) { 528 String version = properties.getFixed().getVersion(); 529 String[] paths = properties.getFixed().getPaths(); 530 resolver.addFixedVersionStrategy(version, paths); 531 } 532 if (properties.getContent().isEnabled()) { 533 String[] paths = properties.getContent().getPaths(); 534 resolver.addContentVersionStrategy(paths); 535 } 536 return resolver; 537 } 538 539 } 540 541 static final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { 542 543 private static final Log logger = LogFactory 544 .getLog(WelcomePageHandlerMapping.class); 545 546 private WelcomePageHandlerMapping(Resource welcomePage, 547 String staticPathPattern) { 548 if (welcomePage != null && "/**".equals(staticPathPattern)) { 549 logger.info("Adding welcome page: " + welcomePage); 550 ParameterizableViewController controller = new ParameterizableViewController(); 551 controller.setViewName("forward:index.html"); 552 setRootHandler(controller); 553 setOrder(0); 554 } 555 } 556 557 @Override 558 public Object getHandlerInternal(HttpServletRequest request) throws Exception { 559 for (MediaType mediaType : getAcceptedMediaTypes(request)) { 560 if (mediaType.includes(MediaType.TEXT_HTML)) { 561 return super.getHandlerInternal(request); 562 } 563 } 564 return null; 565 } 566 567 private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) { 568 String acceptHeader = request.getHeader(HttpHeaders.ACCEPT); 569 return MediaType.parseMediaTypes( 570 StringUtils.hasText(acceptHeader) ? acceptHeader : "*/*"); 571 } 572 573 } 574 575 /** 576 * Decorator to make {@link PathExtensionContentNegotiationStrategy} optional 577 * depending on a request attribute. 578 */ 579 static class OptionalPathExtensionContentNegotiationStrategy 580 implements ContentNegotiationStrategy { 581 582 private static final String SKIP_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class 583 .getName() + ".SKIP"; 584 585 private final ContentNegotiationStrategy delegate; 586 587 OptionalPathExtensionContentNegotiationStrategy( 588 ContentNegotiationStrategy delegate) { 589 this.delegate = delegate; 590 } 591 592 @Override 593 public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) 594 throws HttpMediaTypeNotAcceptableException { 595 Object skip = webRequest.getAttribute(SKIP_ATTRIBUTE, 596 RequestAttributes.SCOPE_REQUEST); 597 if (skip != null && Boolean.parseBoolean(skip.toString())) { 598 return Collections.emptyList(); 599 } 600 return this.delegate.resolveMediaTypes(webRequest); 601 } 602 603 } 604 605}