Spring DispatcherServlet lifecycle

The master piece of Spring web framework architecture is dispatcher servlet. We'll focus on it in this article.

In the first part of this article we'll see the main concept on which is based Spring's dispatcher servlet: front controller pattern. The next part will be dedicated to the presentation of execution chain in Spring application. It will be followed by explanation of DispatcherServlet class. In the last part, we'll try to develop a customized dispatcher servlet.

Please note that this article treats DispatcherServlet for Spring's 4.0.1 version. If you use a different one, some of examples can need several adjustments. If it's the case, let me know that in the comment box by specifying the not working snippet and your version of Spring.

What is front controller pattern ?

Before approaching DispatcherServlet directly, we need to know some theoretical basics of mechanism on which it's based. Key idea hidden behind DispatcherServlet is front controller pattern.

This pattern provides one central entry point for a web application. This centralized point regroups the common features for system components. We can find there the handlers for secured resources, language switch, session management, caching or input filtering. As you can deduce right now, this common entry point helps to avoid the code duplicate.

So more technically speaking, front controller pattern is composed by one class which captures all incoming requests. After, every request is analyzed to know which controller and which method should handle the request.

Front controller pattern helps to respond optimally to following interrogations:
- how to centralize authorization and authentication ?
- how to handle properly view rendering ?
- how to dispatch requests to appropriate controllers with URL rewriting mapping ?

5 participants can compose the front controller pattern:
- client: sends requests.
- controller: central point of the application, it captures all requests.
- dispatcher: manages the choice of the view to present to the client.
- view: represents the content rendered to client.
- helper: helps view and/or controller to complete the request processing.

What is execution chain of DispatcherServlet ?

As you have seen previously, front controller pattern has its own execution chain. It means that it has its own logic to handle requests and return the view to client:

  1. Request is sent by client. It arrives to DispatcherServlet class which is Spring's default front controller.
  2. DispatcherServlet uses request handler mapping to discover the controller which will analyze the request. The implementations of org.springframework.web.servlet.HandlerMapping return an instance of org.springframework.web.servlet.HandlerExecutionChain class. This instance contains the array of handler interceptors which can be invoked before or after controller call. You can learn more about interceptors in article about handler interceptors in Spring. If no HandlerExecutionChain is found in all defined handler mappings, it means that Spring wasn't able to match URL with corresponding controllers. An error is thrown at this occasion.
  3. Now the system applies pre-interceptors and calls the controller found by handler mapping. After asking the controller to handle the request, DispatcherServlet applies all defined post-interceptors. At the end of this step, it receives ModelAndView instance from controller.
  4. DispatcherServlet uses now the view's name and sends it into view resolver. This resolver will decide what client should see exactly on the screen. Next, it returns this view to DispatcherServlet which can, at this moment, apply interceptors defined as "callable after view generation".
  5. The last operation is the view's rendering as a response to initial client's request.

What is DispatcherServlet ?

Thanks to schema from previous part, we can simply deduce that DispatcherServlet is a central point for web application based on Spring. It takes incoming request and, with the help of handler mappings, interceptors, controller and view resolver, generates the response to client. So, we can enter to the details of this class and approach its principal points. They're the steps followed by DispatcherServlet when a request is handled:

1. Strategies initialization

DispatcherServlet is a class located at org.springframework.web.servlet package and extending FrameworkServlet abstract class from the same package. It contains some of private fields with resolvers (for locale, view, exceptions or uploaded files), handler mappings and handler adapters. The very important point of DispatcherServlet is the method which initializes the strategies (protected void initStrategies(ApplicationContext context)). This method is invoked with the call of onRefresh method. This last call is made in FrameworkServlet through initServletBean and initWebApplicationContext methods. initServletBean produces an application context with all provided strategies. Each strategy represents an object used by DispatcherServlet to handle an incoming request. They are following initStrategies method for Spring 4:

/**
 * Initialize the strategy objects that this servlet uses.
 * May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}
Note, that if a stategy doesn't exist, an exception NoSuchBeanDefinitionException is catched and a default strategy is taken instead. If this default strategy, initially defined in DispatcherServler.properties file, doesn't exist, an exception BeanInitializationException is thrown. Default strategy recovery is coded as above:
/**
 * Return the default strategy object for the given strategy interface.
 * The default implementation delegates to {@link #getDefaultStrategies},
 * expecting a single object in the list.
 * @param context the current WebApplicationContext
 * @param strategyInterface the strategy interface
 * @return the corresponding strategy object
 * @see #getDefaultStrategies
 */
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
	List<T> strategies = getDefaultStrategies(context, strategyInterface);
	if (strategies.size() != 1) {
		throw new BeanInitializationException(
				"DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
	}
	return strategies.get(0);
}

2. Request handling

FrameworkServlet abstract class extends HttpServletBean from the same package and HttpServletBean extends javax.servlet.http.HttpServlet. As you know, HttpServlet is an abstract class with methods defined to handle every type of HTTP request: doGet (GET request), doPost (POST), doPut (PUT), doDelete (DELETE), doTrace (TRACE), doHead (HEAD), doOptions (OPTIONS). FrameworkServlet overrides all of them by dispatching every incoming request into processRequest(HttpServletRequest request, HttpServletResponse response). processRequest is a protected and final method that constructs LocaleContext and ServletRequestAttributes objects, both accessible after from RequestContextHolder object. All this operation is coded in this way:

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	processRequest(request, response);
}

/**
 * Process this request, publishing an event regardless of the outcome.
 * The actual event handling is performed by the abstract
 * {@link #doService} template method.
 */
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	long startTime = System.currentTimeMillis();
	Throwable failureCause = null;

	LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
	LocaleContext localeContext = buildLocaleContext(request);

	RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
	ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

	initContextHolders(request, localeContext, requestAttributes);

	try {
		doService(request, response);
	}
	catch (ServletException ex) {
		failureCause = ex;
		throw ex;
	}
	catch (IOException ex) {
		failureCause = ex;
		throw ex;
	}
	catch (Throwable ex) {
		failureCause = ex;
		throw new NestedServletException("Request processing failed", ex);
	}

	finally {
		resetContextHolders(request, previousLocaleContext, previousAttributes);
		if (requestAttributes != null) {
			requestAttributes.requestCompleted();
		}

		if (logger.isDebugEnabled()) {
			if (failureCause != null) {
				this.logger.debug("Could not complete request", failureCause);
			}
			else {
				if (asyncManager.isConcurrentHandlingStarted()) {
					logger.debug("Leaving response open for concurrent processing");
				}
				else {
					this.logger.debug("Successfully completed request");
				}
			}
		}

		publishRequestHandledEvent(request, startTime, failureCause);
	}
}

3. Request handling

As you can see, in processRequest's code, after calling initContextHolders method, protected void doService(HttpServletRequest request, HttpServletResponse response) is invoked. doService puts some additional parameters into request (flash maps, context information) and calls protected void doDispatch(HttpServletRequest request, HttpServletResponse response).

The most important part of doDispatch method is handler retrieving. doDispatch calls getHandler() method which analyzes processed requests and returns HandlerExecutionChain instance. This instance contains handler mapping and interceptors. The next thing done by DispatcherServlet is applying pre-handler interceptors (applyPreHandle()). If at least one of them returns false, the request processing stops. Otherwise, the servlet uses handler adapter associated to handler mapping to generate the view object.

doDispatch method:

/**
 * Process the actual dispatching to the handler.
 * The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	try {
		ModelAndView mv = null;
		Exception dispatchException = null;

		try {
			processedRequest = checkMultipart(request);
			multipartRequestParsed = processedRequest != request;

			// Determine handler for the current request.
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null || mappedHandler.getHandler() == null) {
				noHandlerFound(processedRequest, response);
				return;
			}

			// Determine handler adapter for the current request.
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			// Process last-modified header, if supported by the handler.
			String method = request.getMethod();
			boolean isGet = "GET".equals(method);
			if (isGet || "HEAD".equals(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (logger.isDebugEnabled()) {
					String requestUri = urlPathHelper.getRequestUri(request);
					logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
				}
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}

			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

			try {
				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			}
			finally {
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
			}

			applyDefaultViewName(request, mv);
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
			dispatchException = ex;
		}
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Error err) {
		triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
	}
	finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
			// Instead of postHandle and afterCompletion
			mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			return;
		}
		// Clean up any resources used by a multipart request.
		if (multipartRequestParsed) {
			cleanupMultipart(processedRequest);
		}
	}
}

4. View resolving

After getting ModelAndView instance with view to render, doDispatch method calls private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv). The default view name is applied according to defined bean with the name "viewNameTranslator". By default, its implementation is org.springframework.web.servlet.RequestToViewNameTranslator. This default implementation simply translates URL into view name, like for example (taken directly from RequestToViewNameTranslator): http://localhost:8080/admin/index.html will generate the view admin/index.

The next step is the call of post-interceptors.

5. Processing of dispatched request - view rendering

Now the servlet knows which the view should be rendered. It pass to private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) method which makes the last operation - view rendering.

First of all, processDispatchResult checks if they are no exception passed in parameter. It's there are some exception, it defines a new view, specific to error pages. If there are not any exception, the method checks the ModelAndView instance and, if it's not null, calls rendering method.

The rendering method is protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response). Inside, according to the defined view strategy, it looks for a View class instance. It will be responsible for displaying the response. If the View isn't found, an ServletException is thrown. Otherwise, DispatcherServlet calls its render method to display the result.

The interceptors defined as "after completion" are invoked at the last step of request handling.

Here you can find processDispatchResult and render source codes:

/**
 * Handle the result of handler selection and handler invocation, which is
 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
 */
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
		HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

	boolean errorView = false;

	if (exception != null) {
		if (exception instanceof ModelAndViewDefiningException) {
			logger.debug("ModelAndViewDefiningException encountered", exception);
			mv = ((ModelAndViewDefiningException) exception).getModelAndView();
		}
		else {
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			mv = processHandlerException(request, response, handler, exception);
			errorView = (mv != null);
		}
	}

	// Did the handler return a view to render?
	if (mv != null && !mv.wasCleared()) {
		render(mv, request, response);
		if (errorView) {
			WebUtils.clearErrorRequestAttributes(request);
		}
	}
	else {
		if (logger.isDebugEnabled()) {
			logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
					"': assuming HandlerAdapter completed request handling");
		}
	}

	if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
		// Concurrent handling started during a forward
		return;
	}

	if (mappedHandler != null) {
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
}

/**
 * Render the given ModelAndView.
 * This is the last stage in handling a request. It may involve resolving the view by name.
 * @param mv the ModelAndView to render
 * @param request current HTTP servlet request
 * @param response current HTTP servlet response
 * @throws ServletException if view is missing or cannot be resolved
 * @throws Exception if there's a problem rendering the view
 */
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	// Determine locale for request and apply it to the response.
	Locale locale = this.localeResolver.resolveLocale(request);
	response.setLocale(locale);

	View view;
	if (mv.isReference()) {
		// We need to resolve the view name.
		view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
		if (view == null) {
			throw new ServletException(
					"Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" +
							getServletName() + "'");
		}
	}
	else {
		// No need to lookup: the ModelAndView object contains the actual View object.
		view = mv.getView();
		if (view == null) {
			throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
					"View object in servlet with name '" + getServletName() + "'");
		}
	}

	// Delegate to the View object for rendering.
	if (logger.isDebugEnabled()) {
		logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
	}
	try {
		view.render(mv.getModelInternal(), request, response);
	}
	catch (Exception ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '"
					+ getServletName() + "'", ex);
		}
		throw ex;
	}
}

In this part, you need to remember that we defined two contexts: one for application and another one for web application. What is the difference between them ? The application context contains all generic configuration, as services definition, database configuration. The web application context defines all web related components, as controllers or view resolvers.

Custom DispatcherServlet

We have approached only theoretical side of DispatcherServlet. Thanks to this new knowledge, we can pass to practical part of this article and write our own servlet to dispatch the requests. We'll proceed step by step, beginning with capturing request and ending with view rendering.

As we have seen, to capture a request, we need to override the doService method:
public class CustomDispatcherServlet extends FrameworkServlet {
	private static final Logger LOGGER = LoggerFactory.getLogger(CustomDispatcherServlet.class);

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		LOGGER.debug("[CustomDispatcherServlet] I got the request !");
	}
}

In our log file we should find an entry "[CustomDispatcherServlet] I got the request !". OK ? So, let's continue by adding the job made by doDispatch method in DispatcherServlet:

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		LOGGER.debug("[CustomDispatcherServlet] I got the request !");
		try {
			LOGGER.debug("[CustomDispatcherServlet] doService");
			LocaleContext localeContext = buildLocaleContext(request);

			RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
			ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

			initContextHolders(request, localeContext, requestAttributes);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

What does this method do ? First, it construct a current Locale instance for received request. The second step is the initialization of org.springframework.web.context.request.ServletRequestAttributes instance. It's an implementation of RequestAttributes interface, localized at the same level. Thanks to it we can access servlet request's objects and session objects without distinction between session and global session. At the end we invoke initContextHolders() method which initializes context holders, ie the objects which permi to access request attributes and locale from everywhere from application through LocaleContextHolder and RequestContextHolder static methods (respectively: getLocaleContext and getRequestAttributes).

Actually request is intercepted and some of basic stuff is set to ready state. But we haven't neither execution chain nor handler adapter. We can retreive them thanks to this code:

private HandlerExecutionChain getHandlerExecutionChain(HttpServletRequest request) throws Exception {
	for (HandlerMapping mapper : this.handlerMappings) {
		HandlerExecutionChain executionChain = mapper.getHandler(request);
		if (executionChain != null) {
			return executionChain;
		}
	}
	throw new Exception("Execution chain wasn't be found in provided handler mappings: "+this.handlerMappings);
}

Thanks to execution chain we can access to handler adapter which will handle current request. It's made with this method:

@Override
protected HandlerAdapter getHandlerAdapter(Object executionChain) throws ServletException {
	for (HandlerAdapter adapter : this.handlerAdapters) {
		LOGGER.debug("[CustomDispatcherServlet] "+adapter + " is instanceof HandlerMethod ? "+(adapter instanceof HandlerMethod));
		if (adapter.supports(executionChain)) {
			return adapter;
		}
	}
	throw new  ServletException("Handler adapter was not found from adapters list :"+this.handlerAdapters);
}

Only adapters defined in application context (this.handlerAdapter) and supporting generated execution chain (adapter.supports) can be returned. Now, with execution chain and adapter, we can return to our doService method and manipulate them to render a view:

ModelAndView modelView = adapter.handle(request, response, executionChain.getHandler());
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);

View view = null;
if (!modelView.isReference()) {
	throw new UnsupportedOperationException("Only view models defined as references can be used in this servlet");
}
for (ViewResolver viewResolver : this.viewResolvers) {
	view = viewResolver.resolveViewName(modelView.getViewName(), locale);
	if (view != null) {
		break;
	}
}
if (view == null) {
	throw new ServletException("Could not resolve view with name '" + modelView.getViewName() + "' in servlet with name '" + getServletName() + "'");
}
view.render(modelView.getModelMap(), request, response);

View rendering is simplified in our servlet. In fact, we handle only ModelAndView objects defined as references. It means that ModelAndView is an instance of String which represents a view model to resolve, for exemple: a template entry in Apache Tiles configuration file. After this check, we iterate through current view resolvers. The first resolver which is able to generate a View's instance, is considered as the resolver used in treated request. At the end we check if the view was correctly generated. If it's the case, we invoke its render() method to show the request treatement result in the screen.

In this part, we restricted the description and the code parts to strict minimum. To see a little bit more points, also very interesting, please download a sample Spring dispatcher servlet class and read carefully the comments.

This article presents the central point of Spring web application, a dispatcher servlet. Remember that it's a class which will handle all incoming requests and render the view to the user. Before rewrite it, you should be familiar with the concepts like execution chain, handler mapping or handler adapter. Remember that the first one defines all elements invoked in dispatching process. A handler mapping is a system which maps incoming request (its URL) into appropriate controller. The last mentioned element, a handler adapter, is an object that will dispatch the request to the controller found by handler mapping. The result of this dispatch is an instance of ModelAndView class which is used later to generate and render the view.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!