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.servlet; 018 019import java.util.Arrays; 020import java.util.List; 021 022import javax.servlet.MultipartConfigElement; 023import javax.servlet.ServletRegistration; 024 025import org.springframework.beans.factory.ObjectProvider; 026import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 027import org.springframework.boot.autoconfigure.AutoConfigureAfter; 028import org.springframework.boot.autoconfigure.AutoConfigureOrder; 029import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 030import org.springframework.boot.autoconfigure.condition.ConditionMessage; 031import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style; 032import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 033import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 034import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 035import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 036import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 037import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; 038import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 039import org.springframework.boot.autoconfigure.http.HttpProperties; 040import org.springframework.boot.context.properties.EnableConfigurationProperties; 041import org.springframework.boot.web.servlet.ServletRegistrationBean; 042import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 043import org.springframework.context.annotation.Bean; 044import org.springframework.context.annotation.ConditionContext; 045import org.springframework.context.annotation.Conditional; 046import org.springframework.context.annotation.Configuration; 047import org.springframework.context.annotation.Import; 048import org.springframework.core.Ordered; 049import org.springframework.core.annotation.Order; 050import org.springframework.core.type.AnnotatedTypeMetadata; 051import org.springframework.web.multipart.MultipartResolver; 052import org.springframework.web.servlet.DispatcherServlet; 053 054/** 055 * {@link EnableAutoConfiguration Auto-configuration} for the Spring 056 * {@link DispatcherServlet}. Should work for a standalone application where an embedded 057 * web server is already present and also for a deployable application using 058 * {@link SpringBootServletInitializer}. 059 * 060 * @author Phillip Webb 061 * @author Dave Syer 062 * @author Stephane Nicoll 063 * @author Brian Clozel 064 */ 065@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 066@Configuration 067@ConditionalOnWebApplication(type = Type.SERVLET) 068@ConditionalOnClass(DispatcherServlet.class) 069@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) 070public class DispatcherServletAutoConfiguration { 071 072 /* 073 * The bean name for a DispatcherServlet that will be mapped to the root URL "/" 074 */ 075 public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet"; 076 077 /* 078 * The bean name for a ServletRegistrationBean for the DispatcherServlet "/" 079 */ 080 public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration"; 081 082 @Configuration 083 @Conditional(DefaultDispatcherServletCondition.class) 084 @ConditionalOnClass(ServletRegistration.class) 085 @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class }) 086 protected static class DispatcherServletConfiguration { 087 088 private final HttpProperties httpProperties; 089 090 private final WebMvcProperties webMvcProperties; 091 092 public DispatcherServletConfiguration(HttpProperties httpProperties, 093 WebMvcProperties webMvcProperties) { 094 this.httpProperties = httpProperties; 095 this.webMvcProperties = webMvcProperties; 096 } 097 098 @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) 099 public DispatcherServlet dispatcherServlet() { 100 DispatcherServlet dispatcherServlet = new DispatcherServlet(); 101 dispatcherServlet.setDispatchOptionsRequest( 102 this.webMvcProperties.isDispatchOptionsRequest()); 103 dispatcherServlet.setDispatchTraceRequest( 104 this.webMvcProperties.isDispatchTraceRequest()); 105 dispatcherServlet.setThrowExceptionIfNoHandlerFound( 106 this.webMvcProperties.isThrowExceptionIfNoHandlerFound()); 107 dispatcherServlet.setEnableLoggingRequestDetails( 108 this.httpProperties.isLogRequestDetails()); 109 return dispatcherServlet; 110 } 111 112 @Bean 113 @ConditionalOnBean(MultipartResolver.class) 114 @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) 115 public MultipartResolver multipartResolver(MultipartResolver resolver) { 116 // Detect if the user has created a MultipartResolver but named it incorrectly 117 return resolver; 118 } 119 120 } 121 122 @Configuration 123 @Conditional(DispatcherServletRegistrationCondition.class) 124 @ConditionalOnClass(ServletRegistration.class) 125 @EnableConfigurationProperties(WebMvcProperties.class) 126 @Import(DispatcherServletConfiguration.class) 127 protected static class DispatcherServletRegistrationConfiguration { 128 129 private final WebMvcProperties webMvcProperties; 130 131 private final MultipartConfigElement multipartConfig; 132 133 public DispatcherServletRegistrationConfiguration( 134 WebMvcProperties webMvcProperties, 135 ObjectProvider<MultipartConfigElement> multipartConfigProvider) { 136 this.webMvcProperties = webMvcProperties; 137 this.multipartConfig = multipartConfigProvider.getIfAvailable(); 138 } 139 140 @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) 141 @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) 142 public DispatcherServletRegistrationBean dispatcherServletRegistration( 143 DispatcherServlet dispatcherServlet) { 144 DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean( 145 dispatcherServlet, this.webMvcProperties.getServlet().getPath()); 146 registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); 147 registration.setLoadOnStartup( 148 this.webMvcProperties.getServlet().getLoadOnStartup()); 149 if (this.multipartConfig != null) { 150 registration.setMultipartConfig(this.multipartConfig); 151 } 152 return registration; 153 } 154 155 } 156 157 @Order(Ordered.LOWEST_PRECEDENCE - 10) 158 private static class DefaultDispatcherServletCondition extends SpringBootCondition { 159 160 @Override 161 public ConditionOutcome getMatchOutcome(ConditionContext context, 162 AnnotatedTypeMetadata metadata) { 163 ConditionMessage.Builder message = ConditionMessage 164 .forCondition("Default DispatcherServlet"); 165 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); 166 List<String> dispatchServletBeans = Arrays.asList(beanFactory 167 .getBeanNamesForType(DispatcherServlet.class, false, false)); 168 if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) { 169 return ConditionOutcome.noMatch(message.found("dispatcher servlet bean") 170 .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)); 171 } 172 if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) { 173 return ConditionOutcome 174 .noMatch(message.found("non dispatcher servlet bean") 175 .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)); 176 } 177 if (dispatchServletBeans.isEmpty()) { 178 return ConditionOutcome 179 .match(message.didNotFind("dispatcher servlet beans").atAll()); 180 } 181 return ConditionOutcome.match(message 182 .found("dispatcher servlet bean", "dispatcher servlet beans") 183 .items(Style.QUOTE, dispatchServletBeans) 184 .append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)); 185 } 186 187 } 188 189 @Order(Ordered.LOWEST_PRECEDENCE - 10) 190 private static class DispatcherServletRegistrationCondition 191 extends SpringBootCondition { 192 193 @Override 194 public ConditionOutcome getMatchOutcome(ConditionContext context, 195 AnnotatedTypeMetadata metadata) { 196 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); 197 ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory); 198 if (!outcome.isMatch()) { 199 return outcome; 200 } 201 return checkServletRegistration(beanFactory); 202 } 203 204 private ConditionOutcome checkDefaultDispatcherName( 205 ConfigurableListableBeanFactory beanFactory) { 206 List<String> servlets = Arrays.asList(beanFactory 207 .getBeanNamesForType(DispatcherServlet.class, false, false)); 208 boolean containsDispatcherBean = beanFactory 209 .containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); 210 if (containsDispatcherBean 211 && !servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) { 212 return ConditionOutcome 213 .noMatch(startMessage().found("non dispatcher servlet") 214 .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)); 215 } 216 return ConditionOutcome.match(); 217 } 218 219 private ConditionOutcome checkServletRegistration( 220 ConfigurableListableBeanFactory beanFactory) { 221 ConditionMessage.Builder message = startMessage(); 222 List<String> registrations = Arrays.asList(beanFactory 223 .getBeanNamesForType(ServletRegistrationBean.class, false, false)); 224 boolean containsDispatcherRegistrationBean = beanFactory 225 .containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME); 226 if (registrations.isEmpty()) { 227 if (containsDispatcherRegistrationBean) { 228 return ConditionOutcome 229 .noMatch(message.found("non servlet registration bean").items( 230 DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)); 231 } 232 return ConditionOutcome 233 .match(message.didNotFind("servlet registration bean").atAll()); 234 } 235 if (registrations 236 .contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) { 237 return ConditionOutcome.noMatch(message.found("servlet registration bean") 238 .items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)); 239 } 240 if (containsDispatcherRegistrationBean) { 241 return ConditionOutcome 242 .noMatch(message.found("non servlet registration bean").items( 243 DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)); 244 } 245 return ConditionOutcome.match(message.found("servlet registration beans") 246 .items(Style.QUOTE, registrations).append("and none is named " 247 + DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)); 248 } 249 250 private ConditionMessage.Builder startMessage() { 251 return ConditionMessage.forCondition("DispatcherServlet Registration"); 252 } 253 254 } 255 256}