001/* 002 * Copyright 2012-2018 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.reactive; 018 019import java.time.Duration; 020import java.util.Collection; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024 025import org.springframework.beans.factory.ListableBeanFactory; 026import org.springframework.beans.factory.ObjectProvider; 027import org.springframework.boot.autoconfigure.AutoConfigureAfter; 028import org.springframework.boot.autoconfigure.AutoConfigureOrder; 029import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 030import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 031import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 032import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 033import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 034import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration; 035import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; 036import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; 037import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain; 038import org.springframework.boot.autoconfigure.web.ResourceProperties; 039import org.springframework.boot.autoconfigure.web.format.WebConversionService; 040import org.springframework.boot.context.properties.EnableConfigurationProperties; 041import org.springframework.boot.web.codec.CodecCustomizer; 042import org.springframework.boot.web.reactive.filter.OrderedHiddenHttpMethodFilter; 043import org.springframework.context.annotation.Bean; 044import org.springframework.context.annotation.Configuration; 045import org.springframework.context.annotation.Import; 046import org.springframework.core.Ordered; 047import org.springframework.core.convert.converter.Converter; 048import org.springframework.core.convert.converter.GenericConverter; 049import org.springframework.format.Formatter; 050import org.springframework.format.FormatterRegistry; 051import org.springframework.format.support.FormattingConversionService; 052import org.springframework.http.codec.ServerCodecConfigurer; 053import org.springframework.util.ClassUtils; 054import org.springframework.validation.Validator; 055import org.springframework.web.filter.reactive.HiddenHttpMethodFilter; 056import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; 057import org.springframework.web.reactive.config.EnableWebFlux; 058import org.springframework.web.reactive.config.ResourceHandlerRegistration; 059import org.springframework.web.reactive.config.ResourceHandlerRegistry; 060import org.springframework.web.reactive.config.ViewResolverRegistry; 061import org.springframework.web.reactive.config.WebFluxConfigurationSupport; 062import org.springframework.web.reactive.config.WebFluxConfigurer; 063import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; 064import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; 065import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; 066import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; 067import org.springframework.web.reactive.result.view.ViewResolver; 068 069/** 070 * {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebFlux WebFlux}. 071 * 072 * @author Brian Clozel 073 * @author Rob Winch 074 * @author Stephane Nicoll 075 * @author Andy Wilkinson 076 * @author Phillip Webb 077 * @author EddĂș MelĂ©ndez 078 * @author Artsiom Yudovin 079 * @since 2.0.0 080 */ 081@Configuration 082@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) 083@ConditionalOnClass(WebFluxConfigurer.class) 084@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class }) 085@AutoConfigureAfter({ ReactiveWebServerFactoryAutoConfiguration.class, 086 CodecsAutoConfiguration.class, ValidationAutoConfiguration.class }) 087@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) 088public class WebFluxAutoConfiguration { 089 090 @Bean 091 @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) 092 @ConditionalOnProperty(prefix = "spring.webflux.hiddenmethod.filter", name = "enabled", matchIfMissing = true) 093 public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { 094 return new OrderedHiddenHttpMethodFilter(); 095 } 096 097 @Configuration 098 @EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class }) 099 @Import({ EnableWebFluxConfiguration.class }) 100 public static class WebFluxConfig implements WebFluxConfigurer { 101 102 private static final Log logger = LogFactory.getLog(WebFluxConfig.class); 103 104 private final ResourceProperties resourceProperties; 105 106 private final WebFluxProperties webFluxProperties; 107 108 private final ListableBeanFactory beanFactory; 109 110 private final ObjectProvider<HandlerMethodArgumentResolver> argumentResolvers; 111 112 private final ObjectProvider<CodecCustomizer> codecCustomizers; 113 114 private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; 115 116 private final ObjectProvider<ViewResolver> viewResolvers; 117 118 public WebFluxConfig(ResourceProperties resourceProperties, 119 WebFluxProperties webFluxProperties, ListableBeanFactory beanFactory, 120 ObjectProvider<HandlerMethodArgumentResolver> resolvers, 121 ObjectProvider<CodecCustomizer> codecCustomizers, 122 ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizer, 123 ObjectProvider<ViewResolver> viewResolvers) { 124 this.resourceProperties = resourceProperties; 125 this.webFluxProperties = webFluxProperties; 126 this.beanFactory = beanFactory; 127 this.argumentResolvers = resolvers; 128 this.codecCustomizers = codecCustomizers; 129 this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizer 130 .getIfAvailable(); 131 this.viewResolvers = viewResolvers; 132 } 133 134 @Override 135 public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { 136 this.argumentResolvers.orderedStream().forEach(configurer::addCustomResolver); 137 } 138 139 @Override 140 public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { 141 this.codecCustomizers.orderedStream() 142 .forEach((customizer) -> customizer.customize(configurer)); 143 } 144 145 @Override 146 public void addResourceHandlers(ResourceHandlerRegistry registry) { 147 if (!this.resourceProperties.isAddMappings()) { 148 logger.debug("Default resource handling disabled"); 149 return; 150 } 151 if (!registry.hasMappingForPattern("/webjars/**")) { 152 ResourceHandlerRegistration registration = registry 153 .addResourceHandler("/webjars/**") 154 .addResourceLocations("classpath:/META-INF/resources/webjars/"); 155 configureResourceCaching(registration); 156 customizeResourceHandlerRegistration(registration); 157 } 158 String staticPathPattern = this.webFluxProperties.getStaticPathPattern(); 159 if (!registry.hasMappingForPattern(staticPathPattern)) { 160 ResourceHandlerRegistration registration = registry 161 .addResourceHandler(staticPathPattern).addResourceLocations( 162 this.resourceProperties.getStaticLocations()); 163 configureResourceCaching(registration); 164 customizeResourceHandlerRegistration(registration); 165 } 166 } 167 168 private void configureResourceCaching(ResourceHandlerRegistration registration) { 169 Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); 170 ResourceProperties.Cache.Cachecontrol cacheControl = this.resourceProperties 171 .getCache().getCachecontrol(); 172 if (cachePeriod != null && cacheControl.getMaxAge() == null) { 173 cacheControl.setMaxAge(cachePeriod); 174 } 175 registration.setCacheControl(cacheControl.toHttpCacheControl()); 176 } 177 178 @Override 179 public void configureViewResolvers(ViewResolverRegistry registry) { 180 this.viewResolvers.orderedStream().forEach(registry::viewResolver); 181 } 182 183 @Override 184 public void addFormatters(FormatterRegistry registry) { 185 for (Converter<?, ?> converter : getBeansOfType(Converter.class)) { 186 registry.addConverter(converter); 187 } 188 for (GenericConverter converter : getBeansOfType(GenericConverter.class)) { 189 registry.addConverter(converter); 190 } 191 for (Formatter<?> formatter : getBeansOfType(Formatter.class)) { 192 registry.addFormatter(formatter); 193 } 194 } 195 196 private <T> Collection<T> getBeansOfType(Class<T> type) { 197 return this.beanFactory.getBeansOfType(type).values(); 198 } 199 200 private void customizeResourceHandlerRegistration( 201 ResourceHandlerRegistration registration) { 202 if (this.resourceHandlerRegistrationCustomizer != null) { 203 this.resourceHandlerRegistrationCustomizer.customize(registration); 204 } 205 206 } 207 208 } 209 210 /** 211 * Configuration equivalent to {@code @EnableWebFlux}. 212 */ 213 @Configuration 214 public static class EnableWebFluxConfiguration 215 extends DelegatingWebFluxConfiguration { 216 217 private final WebFluxProperties webFluxProperties; 218 219 private final WebFluxRegistrations webFluxRegistrations; 220 221 public EnableWebFluxConfiguration(WebFluxProperties webFluxProperties, 222 ObjectProvider<WebFluxRegistrations> webFluxRegistrations) { 223 this.webFluxProperties = webFluxProperties; 224 this.webFluxRegistrations = webFluxRegistrations.getIfUnique(); 225 } 226 227 @Bean 228 @Override 229 public FormattingConversionService webFluxConversionService() { 230 WebConversionService conversionService = new WebConversionService( 231 this.webFluxProperties.getDateFormat()); 232 addFormatters(conversionService); 233 return conversionService; 234 } 235 236 @Bean 237 @Override 238 public Validator webFluxValidator() { 239 if (!ClassUtils.isPresent("javax.validation.Validator", 240 getClass().getClassLoader())) { 241 return super.webFluxValidator(); 242 } 243 return ValidatorAdapter.get(getApplicationContext(), getValidator()); 244 } 245 246 @Override 247 protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() { 248 if (this.webFluxRegistrations != null && this.webFluxRegistrations 249 .getRequestMappingHandlerAdapter() != null) { 250 return this.webFluxRegistrations.getRequestMappingHandlerAdapter(); 251 } 252 return super.createRequestMappingHandlerAdapter(); 253 } 254 255 @Override 256 protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { 257 if (this.webFluxRegistrations != null && this.webFluxRegistrations 258 .getRequestMappingHandlerMapping() != null) { 259 return this.webFluxRegistrations.getRequestMappingHandlerMapping(); 260 } 261 return super.createRequestMappingHandlerMapping(); 262 } 263 264 } 265 266 @Configuration 267 @ConditionalOnEnabledResourceChain 268 static class ResourceChainCustomizerConfiguration { 269 270 @Bean 271 public ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() { 272 return new ResourceChainResourceHandlerRegistrationCustomizer(); 273 } 274 275 } 276 277}