org.springframework.web.servlet.support.AbstractDispatcherServletInitializer Maven / Gradle / Ivy
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.support;
import java.util.EnumSet;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterRegistration;
import jakarta.servlet.FilterRegistration.Dynamic;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRegistration;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.Conventions;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FrameworkServlet;
/**
* Base class for {@link org.springframework.web.WebApplicationInitializer}
* implementations that register a {@link DispatcherServlet} in the servlet context.
*
* Most applications should consider extending the Spring Java config subclass
* {@link AbstractAnnotationConfigDispatcherServletInitializer}.
*
* @author Arjen Poutsma
* @author Chris Beams
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 3.2
*/
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
/**
* The default servlet name. Can be customized by overriding {@link #getServletName}.
*/
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
/**
* Register a {@link DispatcherServlet} against the given servlet context.
*
This method will create a {@code DispatcherServlet} with the name returned by
* {@link #getServletName()}, initializing it with the application context returned
* from {@link #createServletApplicationContext()}, and mapping it to the patterns
* returned from {@link #getServletMappings()}.
*
Further customization can be achieved by overriding {@link
* #customizeRegistration(ServletRegistration.Dynamic)} or
* {@link #createDispatcherServlet(WebApplicationContext)}.
* @param servletContext the context to register the servlet against
*/
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.state(StringUtils.hasLength(servletName), "getServletName() must not return null or empty");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.state(servletAppContext != null, "createServletApplicationContext() must not return null");
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.state(dispatcherServlet != null, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
/**
* Return the name under which the {@link DispatcherServlet} will be registered.
* Defaults to {@link #DEFAULT_SERVLET_NAME}.
* @see #registerDispatcherServlet(ServletContext)
*/
protected String getServletName() {
return DEFAULT_SERVLET_NAME;
}
/**
* Create a servlet application context to be provided to the {@code DispatcherServlet}.
*
The returned context is delegated to Spring's
* {@link DispatcherServlet#DispatcherServlet(WebApplicationContext)}. As such,
* it typically contains controllers, view resolvers, locale resolvers, and other
* web-related beans.
* @see #registerDispatcherServlet(ServletContext)
*/
protected abstract WebApplicationContext createServletApplicationContext();
/**
* Create a {@link DispatcherServlet} (or other kind of {@link FrameworkServlet}-derived
* dispatcher) with the specified {@link WebApplicationContext}.
*
Note: This allows for any {@link FrameworkServlet} subclass as of 4.2.3.
* Previously, it insisted on returning a {@link DispatcherServlet} or subclass thereof.
*/
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
/**
* Specify application context initializers to be applied to the servlet-specific
* application context that the {@code DispatcherServlet} is being created with.
* @since 4.2
* @see #createServletApplicationContext()
* @see DispatcherServlet#setContextInitializers
* @see #getRootApplicationContextInitializers()
*/
@Nullable
protected ApplicationContextInitializer[] getServletApplicationContextInitializers() {
return null;
}
/**
* Specify the servlet mapping(s) for the {@code DispatcherServlet} —
* for example {@code "/"}, {@code "/app"}, etc.
* @see #registerDispatcherServlet(ServletContext)
*/
protected abstract String[] getServletMappings();
/**
* Specify filters to add and map to the {@code DispatcherServlet}.
* @return an array of filters or {@code null}
* @see #registerServletFilter(ServletContext, Filter)
*/
@Nullable
protected Filter[] getServletFilters() {
return null;
}
/**
* Add the given filter to the ServletContext and map it to the
* {@code DispatcherServlet} as follows:
*
* - a default filter name is chosen based on its concrete type
*
- the {@code asyncSupported} flag is set depending on the
* return value of {@link #isAsyncSupported() asyncSupported}
*
- a filter mapping is created with dispatcher types {@code REQUEST},
* {@code FORWARD}, {@code INCLUDE}, and conditionally {@code ASYNC} depending
* on the return value of {@link #isAsyncSupported() asyncSupported}
*
* If the above defaults are not suitable or insufficient, override this
* method and register filters directly with the {@code ServletContext}.
* @param servletContext the servlet context to register filters with
* @param filter the filter to be registered
* @return the filter registration
*/
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
String filterName = Conventions.getVariableName(filter);
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
int counter = 0;
while (registration == null) {
if (counter == 100) {
throw new IllegalStateException("Failed to register filter with name '" + filterName + "'. " +
"Check if there is another filter registered under the same name.");
}
registration = servletContext.addFilter(filterName + "#" + counter, filter);
counter++;
}
}
registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
return registration;
}
private EnumSet getDispatcherTypes() {
return (isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}
/**
* A single place to control the {@code asyncSupported} flag for the
* {@code DispatcherServlet} and all filters added via {@link #getServletFilters()}.
* The default value is "true".
*/
protected boolean isAsyncSupported() {
return true;
}
/**
* Optionally perform further registration customization once
* {@link #registerDispatcherServlet(ServletContext)} has completed.
* @param registration the {@code DispatcherServlet} registration to be customized
* @see #registerDispatcherServlet(ServletContext)
*/
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
}
}