Do We Use DispatcherServlet?
How deep can DispatcherServlet Depth go? Recursion of recursion of recursion
This post has been translated from Korean to English by Gemini CLI.
Warning⚠️ This content is very long. It may be incorrect. This content was written by myself while digging into the code because I couldn’t clearly answer the question about Spring’s web flow during the KCD interview. If there is any incorrect content, please leave a comment or contact me at
joyson5582@gmail.com!
DispatcherServlet
What is DispatcherServlet? You may have heard of it, but you may have never seen it in actual code. (Of course, there is content that implements something similar in the Wooteco mission java-mvc. 🥲)
Then, I will first leave a blog that defines and explains it well. (I will proceed with the part where I directly explore the code)
It is called a Front Controller that first receives all requests coming through the HTTP protocol and delegates them to the appropriate controller. Source: Mangkyu Developer’s Blog
In Spring, RequestMapping, ServletRequest, and @RequestBody annotations that we commonly use are all managed by this DispatcherServlet to execute requests.
Let’s look at what codes it goes through.
Official Documentation - DispatcherServlet
doDispatch
It receives a request, finds a handler, executes the handler method, and returns a response. - Official Documentation Therefore, it has Request and Response as parameters.
First, excluding the Tomcat part, I will cover from the part where
HttpServletRequestandHttpServletResponsecome in.
I will explain only simple requests, excluding all additional elements such as Multipart, AsyncManager, CORS, and Cache.
1
2
3
4
5
6
7
8
9
10
11
12
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
try {
try {
mappedHandler = getHandler(processedRequest);
...
}
}
}
As such, after assigning values, it gets the handler for the request.
DispatcherServlet - getHandler
1
2
3
4
5
6
7
8
9
10
11
12
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
DispatcherServlet gets the handler that can process the request from the HandlerMapping List it has.
1
2
3
4
public interface HandlerMapping {
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
AbstractHandlerMapping - getHandler
There are really various implementations for the interface, but I will explain it through AbstractHandlerMapping. - Official Documentation (Because most implementations inherit this AbstractHandlerMapping)
Spring used interface - abstract format very often like this. (To define what methods need to be implemented and then enable common logic reuse.)
1
2
3
4
5
6
7
8
9
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
...
}
@Nullable
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
As such, it uses a method called getHandlerInternal and it is also abstracted.
AbstractHandlerMethodMapping - getHandlerInternal
I will explain it through AbstractHandlerMethodMapping, the abstract implementation that maps the most commonly used @RequestMapping. - Official Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.getHandlerMethod();
}
}
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping)));
}
}
It continuously parses the path to check if there is matching information + finds if there is a handler to perform. - lookupHandlerMethod Then, it finds something called HandlerMethod and returns it through a method called createWithResolvedBean().
HandlerMethod
It is a class that inherits AnnotationMethod. - HandlerMethod - AnnotatedMethod It is a class that makes it easier to manage handler (Controller) methods that have various annotations (RequestParam, RequestBody …) in Spring.
1
2
3
4
5
6
7
8
9
10
11
private HandlerMethod(HandlerMethod handlerMethod, @Nullable Object handler, boolean initValidateFlags) {
super(handlerMethod);
...
}
protected AnnotatedMethod(AnnotatedMethod annotatedMethod) {
this.method = annotatedMethod.method;
this.bridgedMethod = annotatedMethod.bridgedMethod;
this.parameters = annotatedMethod.parameters;
this.inheritedParameterAnnotations = annotatedMethod.inheritedParameterAnnotations;
}
- Method to execute
- Method parameters
- Stores annotation information for method parameters
1
2
3
4
5
6
7
8
public HandlerMethod createWithResolvedBean() {
Object handler = this.bean;
Object var3 = this.bean;
if (var3 instanceof String beanName) {
handler = this.beanFactory.getBean(beanName);
}
return new HandlerMethod(this, handler, false);
}
It initializes the bean instance (controller) through the bean factory and then returns HandlerMethod. That is, this HandlerMethod is an executable object. (Because it has a bean)
After AbstractHandlerMethodMapping - getHandlerInternal
Up to this point, it gets HandlerMethod or something else through getHandlerInternal.
Currently,
AbstractHandlerMethodMappinggetsHandlerMethod, but otherHandlerMappings may get something else.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
if (handler instanceof String handlerName) {
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
return executionChain;
}
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain handlerExecutionChain ?
handlerExecutionChain : new HandlerExecutionChain(handler));
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor mappedInterceptor) {
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
If the fetched Handler is null, it returns the default, or null if there is no default. And it adds interceptors to include the Handler and execute it, then returns HandlerExecutionChain. - Official Documentation
Through this process,
- Executable method
- Interceptor to be executed with the method It returns
HandlerExecutionChainwith.
DispatcherServlet - getHandlerAdapter, handle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
...
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
public interface HandlerAdapter {
boolean supports(Object handler);
}
It gets a value called HandlerAdapter and processes the handler through it. Before that, the interceptor’s preHandle operates, and after the request ends, postHandle operates.
AbstractHandlerMethodAdapter - supports
Similar to the explanation using AbstractHandlerMethodMapping above, I will explain it through the abstract class AbstractHandlerMethodAdapter. - Official Documentation It finds and retrieves an adapter that can process the handler.
1
2
3
4
5
6
7
8
9
10
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod handlerMethod && supportsInternal(handlerMethod));
}
// RequestMappingHandlerAdapter
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
If there is RequestMapping, it always returns true. (Because HandlerMethod itself is guaranteed to have it)
AbstractHandlerMethodAdapter - handle
1
2
3
4
5
6
7
8
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
Similar to AbstractHandlerMethodMapping, there is handleInternal.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
@Nullable
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
...
}
else {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
return mav;
}
It checks the request and executes the handler method.
AbstractHandlerMethodAdapter - InvokeHandlerMethod
Then how does it execute the controller’s method?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = (asyncWebRequest instanceof ServletWebRequest ?
(ServletWebRequest) asyncWebRequest : new ServletWebRequest(request, response));
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer);
return getModelAndView(mavContainer, modelFactory, webRequest);
}
protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
return new ServletInvocableHandlerMethod(handlerMethod);
}
After wrapping it once more with ServletInvocableHandlerMethod, it sets argumentResolvers and returnValueHandlers. Then it calls and handles the method.
ServletInvocableHandlerMethod - invokeAndHandle
It handles handler method calls and method return values. - Official Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// Execute and handle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
...
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
...
throw ex;
}
}
// Parameter validation - request - response validation
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if (this.shouldValidateArguments() && this.methodValidator != null) {
this.methodValidator.applyArgumentValidation(this.getBean(), this.getBridgedMethod(), this.getMethodParameters(), args, this.validationGroups);
}
Object returnValue = this.doInvoke(args);
if (this.shouldValidateReturnValue() && this.methodValidator != null) {
this.methodValidator.applyReturnValueValidation(this.getBean(), this.getBridgedMethod(), this.getReturnType(), returnValue, this.validationGroups);
}
return returnValue;
}
// Inject values into method parameters
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
MethodParameter[] parameters = this.getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
} else {
Object[] args = new Object[parameters.length];
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
if (args[i] == null) {
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var10) {
...
throw ex;
}
}
}
return args;
}
}
It’s quite long, but it only performs the functions described in the comments.
- Inject values into parameters via
resolver. - Validate parameters.
- Execute.
- Validate results.
- Process results.
CustomerArgumentResolver, which we commonly use, and elements like @RequestMapping and @RequestBody are also processed here. If View is not used, mv’s view will be null.
DispatcherServlet - processDispatchResult
1
2
3
4
5
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
...
}
After that, it processes the results.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException mavDefiningException) {
mv = mavDefiningException.getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
...
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
(processHandlerException throws again after doing View-related work.) After additional Model-View related work, doDispatch ends with the interceptor’s afterCompletion.
This would be the flow explained in the picture. Of course, this is only an abstract analysis + additional elements are omitted, so please refer to it as such.
In reality?
Debugging the controller and Step Over yields the following:
- DispatcherServlet.doDispatch -> getHandler
- AbstractHandlerMapping.getHandler -> getHandlerInternal
- Dispatcher.getHandlerAdapter
- AbstractHandlerMethodAdapter.supports
- Interceptors
preHandleexecution - AbstractHandlerMethodAdapter.handle
- RequestMappingHandlerAdapter.handleInternal -> invokdHandlerMethod
- ServletInvocableHandlerMethod.invokdAndHandle -> invokeForRequest
- InvocableHandlerMethod.invokeForRequest -> doInvoke
- Actual Method execution
- InvocableHandlerMethod.doInvoke -> invokeForRequest result return
- RequestMappingHandlerAdapter.invokdHandlerMethod -> handleInternal result return
- AbstractHandlerMethodAdapter.handle result return
- Interceptors
postHandleexecution - DispatcherServlet.processDispatchResult execution
- Interceptors
afterCompletionexecution
You can see that Spring does a lot. (It seems okay to use it without knowing. 🥲)
Features that enable this DispatcherServlet include:
- Tomcat receives requests and converts them to
HttpServletRequest& responds viaHttpServletResponse. - Dependency injection of handlers from the bean container.
I will learn more about this later. Thank you for this long exploration!


