001/* 002 * Copyright 2002-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 * 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.servlet.support; 018 019import java.util.EnumSet; 020 021import javax.servlet.DispatcherType; 022import javax.servlet.Filter; 023import javax.servlet.FilterRegistration; 024import javax.servlet.FilterRegistration.Dynamic; 025import javax.servlet.ServletContext; 026import javax.servlet.ServletException; 027import javax.servlet.ServletRegistration; 028 029import org.springframework.context.ApplicationContextInitializer; 030import org.springframework.core.Conventions; 031import org.springframework.lang.Nullable; 032import org.springframework.util.Assert; 033import org.springframework.util.ObjectUtils; 034import org.springframework.web.context.AbstractContextLoaderInitializer; 035import org.springframework.web.context.WebApplicationContext; 036import org.springframework.web.servlet.DispatcherServlet; 037import org.springframework.web.servlet.FrameworkServlet; 038 039/** 040 * Base class for {@link org.springframework.web.WebApplicationInitializer} 041 * implementations that register a {@link DispatcherServlet} in the servlet context. 042 * 043 * <p>Most applications should consider extending the Spring Java config subclass 044 * {@link AbstractAnnotationConfigDispatcherServletInitializer}. 045 * 046 * @author Arjen Poutsma 047 * @author Chris Beams 048 * @author Rossen Stoyanchev 049 * @author Juergen Hoeller 050 * @author Stephane Nicoll 051 * @since 3.2 052 */ 053public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer { 054 055 /** 056 * The default servlet name. Can be customized by overriding {@link #getServletName}. 057 */ 058 public static final String DEFAULT_SERVLET_NAME = "dispatcher"; 059 060 061 @Override 062 public void onStartup(ServletContext servletContext) throws ServletException { 063 super.onStartup(servletContext); 064 registerDispatcherServlet(servletContext); 065 } 066 067 /** 068 * Register a {@link DispatcherServlet} against the given servlet context. 069 * <p>This method will create a {@code DispatcherServlet} with the name returned by 070 * {@link #getServletName()}, initializing it with the application context returned 071 * from {@link #createServletApplicationContext()}, and mapping it to the patterns 072 * returned from {@link #getServletMappings()}. 073 * <p>Further customization can be achieved by overriding {@link 074 * #customizeRegistration(ServletRegistration.Dynamic)} or 075 * {@link #createDispatcherServlet(WebApplicationContext)}. 076 * @param servletContext the context to register the servlet against 077 */ 078 protected void registerDispatcherServlet(ServletContext servletContext) { 079 String servletName = getServletName(); 080 Assert.hasLength(servletName, "getServletName() must not return null or empty"); 081 082 WebApplicationContext servletAppContext = createServletApplicationContext(); 083 Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null"); 084 085 FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); 086 Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null"); 087 dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); 088 089 ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); 090 if (registration == null) { 091 throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " + 092 "Check if there is another servlet registered under the same name."); 093 } 094 095 registration.setLoadOnStartup(1); 096 registration.addMapping(getServletMappings()); 097 registration.setAsyncSupported(isAsyncSupported()); 098 099 Filter[] filters = getServletFilters(); 100 if (!ObjectUtils.isEmpty(filters)) { 101 for (Filter filter : filters) { 102 registerServletFilter(servletContext, filter); 103 } 104 } 105 106 customizeRegistration(registration); 107 } 108 109 /** 110 * Return the name under which the {@link DispatcherServlet} will be registered. 111 * Defaults to {@link #DEFAULT_SERVLET_NAME}. 112 * @see #registerDispatcherServlet(ServletContext) 113 */ 114 protected String getServletName() { 115 return DEFAULT_SERVLET_NAME; 116 } 117 118 /** 119 * Create a servlet application context to be provided to the {@code DispatcherServlet}. 120 * <p>The returned context is delegated to Spring's 121 * {@link DispatcherServlet#DispatcherServlet(WebApplicationContext)}. As such, 122 * it typically contains controllers, view resolvers, locale resolvers, and other 123 * web-related beans. 124 * @see #registerDispatcherServlet(ServletContext) 125 */ 126 protected abstract WebApplicationContext createServletApplicationContext(); 127 128 /** 129 * Create a {@link DispatcherServlet} (or other kind of {@link FrameworkServlet}-derived 130 * dispatcher) with the specified {@link WebApplicationContext}. 131 * <p>Note: This allows for any {@link FrameworkServlet} subclass as of 4.2.3. 132 * Previously, it insisted on returning a {@link DispatcherServlet} or subclass thereof. 133 */ 134 protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) { 135 return new DispatcherServlet(servletAppContext); 136 } 137 138 /** 139 * Specify application context initializers to be applied to the servlet-specific 140 * application context that the {@code DispatcherServlet} is being created with. 141 * @since 4.2 142 * @see #createServletApplicationContext() 143 * @see DispatcherServlet#setContextInitializers 144 * @see #getRootApplicationContextInitializers() 145 */ 146 @Nullable 147 protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() { 148 return null; 149 } 150 151 /** 152 * Specify the servlet mapping(s) for the {@code DispatcherServlet} — 153 * for example {@code "/"}, {@code "/app"}, etc. 154 * @see #registerDispatcherServlet(ServletContext) 155 */ 156 protected abstract String[] getServletMappings(); 157 158 /** 159 * Specify filters to add and map to the {@code DispatcherServlet}. 160 * @return an array of filters or {@code null} 161 * @see #registerServletFilter(ServletContext, Filter) 162 */ 163 @Nullable 164 protected Filter[] getServletFilters() { 165 return null; 166 } 167 168 /** 169 * Add the given filter to the ServletContext and map it to the 170 * {@code DispatcherServlet} as follows: 171 * <ul> 172 * <li>a default filter name is chosen based on its concrete type 173 * <li>the {@code asyncSupported} flag is set depending on the 174 * return value of {@link #isAsyncSupported() asyncSupported} 175 * <li>a filter mapping is created with dispatcher types {@code REQUEST}, 176 * {@code FORWARD}, {@code INCLUDE}, and conditionally {@code ASYNC} depending 177 * on the return value of {@link #isAsyncSupported() asyncSupported} 178 * </ul> 179 * <p>If the above defaults are not suitable or insufficient, override this 180 * method and register filters directly with the {@code ServletContext}. 181 * @param servletContext the servlet context to register filters with 182 * @param filter the filter to be registered 183 * @return the filter registration 184 */ 185 protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) { 186 String filterName = Conventions.getVariableName(filter); 187 Dynamic registration = servletContext.addFilter(filterName, filter); 188 189 if (registration == null) { 190 int counter = 0; 191 while (registration == null) { 192 if (counter == 100) { 193 throw new IllegalStateException("Failed to register filter with name '" + filterName + "'. " + 194 "Check if there is another filter registered under the same name."); 195 } 196 registration = servletContext.addFilter(filterName + "#" + counter, filter); 197 counter++; 198 } 199 } 200 201 registration.setAsyncSupported(isAsyncSupported()); 202 registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName()); 203 return registration; 204 } 205 206 private EnumSet<DispatcherType> getDispatcherTypes() { 207 return (isAsyncSupported() ? 208 EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) : 209 EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE)); 210 } 211 212 /** 213 * A single place to control the {@code asyncSupported} flag for the 214 * {@code DispatcherServlet} and all filters added via {@link #getServletFilters()}. 215 * <p>The default value is "true". 216 */ 217 protected boolean isAsyncSupported() { 218 return true; 219 } 220 221 /** 222 * Optionally perform further registration customization once 223 * {@link #registerDispatcherServlet(ServletContext)} has completed. 224 * @param registration the {@code DispatcherServlet} registration to be customized 225 * @see #registerDispatcherServlet(ServletContext) 226 */ 227 protected void customizeRegistration(ServletRegistration.Dynamic registration) { 228 } 229 230}