Although it has nothing to do with our development, especially when you develop with SpringBoot, these interfaces are getting farther and farther away from you. To tell the truth, if I hadn't glanced at the school courseware this semester, I wouldn't have known it. It was originally hidden from developers using the framework. It's hard for people to hide, but you want us to learn. There's nothing to do.
The following figure is a general map circulated on the Internet, from this article: Understanding of SpringMVC framework
Now learn what these interfaces do by reading the source code
DispathcerServlet
No matter which Web framework or language it is based on, it will provide something that accepts user requests at the front end of the whole system. For the time being, we call it "front-end scheduler". It will parse user requests and schedule the components you write to receive requests. In this way, you can write different components according to different requests. In SpringMVC, the DispathcerServlet is the front-end scheduler, and the Controller is the component you write to process requests.
SpringMVC is also based on the Servlet API of JavaWeb. Therefore, it uses a Servlet to receive all requests. It is like a bridge, with Servlet API on one side and SpringMVC on the other. It translates the words in the Servlet world into the general language in the framework.
Since it's a Servlet, let's look at its doService method:
@Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); // ... Omit some code try { doDispatch(request, response); } // ... Omit some more code }
doDispatch is called to perform scheduling. There is too much code in doDispatch, and I still have a lot after streamlining, so I write comments in the code:
@SuppressWarnings("deprecation") protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; // Remember its type, which is a Handler execution chain HandlerExecutionChain mappedHandler = null; ModelAndView mv = null; Exception dispatchException = null; // ... Omit code try { // processedRequest is a request object that has been processed // Here is to obtain a Handler object (actually a Handler execution chain) that can process the request according to the request // Handler is used to process requests mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Get a HandlerAdapter object according to the Handler object // Why do we need this floor? We'll talk about it later HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Call the Handler's preprocessing method `preHandle before actually processing the request` if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually call the Handler. What is called here is the Handler's adapter object, HandlerAdapter, which will return a ModelAndView mv = ha.handle(processedRequest, response, // After the request is processed, the 'postHandle' method of the Handler is called here mappedHandler.applyPostHandle(processedRequest, response, mv); } // Record exception catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } // Process the processing results obtained from the above tossing together and return them to the front end processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }
At present, the process of dispatcher servlet scheduling a request is:
- Get the Handler object that can be processed according to the request
- Get its adapter object HandlerAdapter according to the Handler object
- Call the preHandle of the Handler to preprocess the request
- Call the handle of HandlerAdapter to actually process the request and return ModelAndView
- Call the Handler's postHandle to deal with the aftermath
- If there is an exception in the above process, record it
- Finally, according to the status of request object, response object, Handler object, ModelAndView object and exception object, a result is returned to the front end
From these processes, we can know that the Handler is the object that actually processes the request, and according to its type, HandlerExecutionChain, we can guess that the actual request processing may be not only a processor, but a series of processors. HandlerAdapter is a repackaging of the object, and the adapter mode should be used to be compatible with some incompatible interfaces.
Now let's continue to deepen
How to implement getHandler
The following is the code of getHandler. We can see that it traverses an iteratable object called handlerMappings in the class, which proves that the DispathcerServlet maintains a series of HandlerMapping objects for mapping requests to handlers.
Moreover, the task of getHandler is to find one that can handle the current request from these HandlerMapping and return it. Otherwise, it will return null.
@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; }
What is HandlerMapping?
public interface HandlerMapping { // ... Omit some irrelevant methods @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; }
The getHandler method returns a Handler and any Interceptors for the request object in the parameter. The selection may be based on the request URL, session state, or other factors of any implementation class selection.
The returned HandlerExecutionChain contains a handler Object of Object type instead of any interface type, so that the processor can be free from any constraints. For example, a HandlerAdapter can be written as a handler Object that allows the use of other frameworks.
How to implement HandlerExecutionChain
Through the above analysis, we have known that the HandlerExecutionChain contains the actual Handler object, which is used to process requests. This object is not coupled with the processor object of any framework, and then it also contains a series of handlerinterceptors for the request.
private final Object handler; private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
We know that the HandlerInterceptor object of SpringMVC is used to intercept a request. It can do some processing before and after a request is executed. This is the class we often use. It doesn't matter if you don't know. Now you know its function.
We can see that the HandlerExecutionChain provides some methods. applyPreHandle is used to call the preHandle methods of all interceptors. If the method returns false, it proves that the interceptor does not want the request to continue to be passed to the next processor in the chain, and the request should be intercepted, so far. So it determines the return value and stops execution when it returns false. Other applyPostHandle and triggerAfterCompletion are also similar. This is the responsibility chain design pattern.
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { for (int i = 0; i < this.interceptorList.size(); i++) { HandlerInterceptor interceptor = this.interceptorList.get(i); if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } return true; } /** * Apply postHandle methods of registered interceptors. */ void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { /* ... */ } void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) { /* ... */ }
Back to doDispatch
Now, can you understand the code of the highlighted part below in doDispatch without comments
+mappedHandler = getHandler(processedRequest); HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); +if (!mappedHandler.applyPreHandle(processedRequest, response)) { + return; +} mv = ha.handle(processedRequest, response, +mappedHandler.applyPostHandle(processedRequest, response, mv);
At present, we know that HandlerMapping is used to map requests to a HandlerExecutionChain, and HandlerExecutionChain mainly executes all interceptors of the request, which is used to intercept and deal with the aftermath of the request, and it saves the actual Handler Object, which is of Object type and is not coupled with the processor class in any framework, which gives other frameworks the ability to integrate into SpringMVC.
HandlerAdapter
Now there is only HandlerAdapter left in our territory. Although I am full of thoughts, my mother asked me to eat. Seeing this, you might as well have a rest.
Since SpringMVC selects the Handler Object of Object type to be compatible with other frameworks, there must be an adapter Object to adapt this Handler to SpringMVC. If it does not undergo any processing, it is impossible to expect the processors in other frameworks to be fully compatible with the interfaces in SpringMVC. So the adapter mode is used here.
getHandlerAdapter method
It is not difficult to see that, like HandlerMapping, the DispatcherServlet also maintains an iterative structure of handlerAdapters - handlerAdapters.
The getHandlerAdapter method traverses the adapter chain, calls their supports method, and passes in the handler of Object type to see whether the adapter fits the processor. It finds the first adapter that fits the processor and returns, otherwise it throws an exception.
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"); }
What does HandlerAdapter look like
The HandlerAdapter interface is very simple. The supports method is used to determine whether the incoming Handler is adapted by the current adapter. The handle method is used to actually process requests. While calling its own Handler object API, it tries its best to adapt to the SpringMVC API, such as returning ModelAndView in Spring. It is the bridge between any Handler (Spring compatible or Spring incompatible) and the Spring framework.
public interface HandlerAdapter { boolean supports(Object handler); @Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; // ... Omit an obsolete method }
support method
For example, you have a self-developed framework FlyMVC. Your framework has its own processor interface FlyHandler, which is added to the HandlerExecutionChain through HandlerMapping. But it is not compatible with Spring framework. In order to make it work with Spring, you need to write a HandlerAdapter, and its supports method may be as follows:
public boolean supports(Object handler) { return handler instanceof FlyHandler; }
Who plays the role of Handler in SpringMVC?
There is no doubt that it is the Controller.
If you see this on the HandlerAdapter class in the new version of Idea, you can see all the implementation classes by clicking:
Click to see that there is a SimpleControllerHandlerAdapter, which must be the HandlerAdapter of the Controller for SpringMVC. Its implementation is extremely simple:
public class SimpleControllerHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof Controller); } @Override @Nullable public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); } // ... Omit an irrelevant method }
If you implement the Controller interface to write a request handler, this HandlerAdapter will be applied. At the same time, because the Controller is a component in SpringMVC, and its handleRequest method returns ModelAndView, its handle method is very simple, and it can't see what conversion between incompatible interfaces has been done. After all, it is a component in SpringMVC.
How to deal with controllers that do not implement the Controller interface?
We know that we don't have to implement interfaces to write controllers in Spring, and the least intrusiveness officially advocated by Spring also encourages us to try not to use interfaces, which will make our components coupled with the Spring framework.
We can write a Controller like this:
@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } }
This Controller does not implement any interfaces. How is it scheduled by DispatcherServlet? Here, I blindly guess that Spring will convert it into an implementation class of a Controller interface, and then continue to use SimpleControllerHandlerAdapter to schedule it according to the above process.
We put a breakpoint here, then run the program and visit /hello:
There is no response at the breakpoint and the request passes directly, which shows that the Controller written without implementing the interface does not work as we thought before. It is completely two processes and does not use the SimpleControllerHandlerAdapter at all.
Now let's put the breakpoint into the DispatcherServlet:
This time we see that handler is an instance of a HandlerMethod object, and its HandlerAdapter is RequestMappingHandlerAdapter:
HandlerMethod
For controllers written in non interface mode, each method handles a request. Therefore, HandlerMethod represents a request method.
RequestMappingHandlerAdapter
It is used to handle the HandlerMethod object marked with @RequestMapping, so it is named.
It is extended from AbstractHandlerMethodAdapter, and the supports method is also completed in the parent class:
// AbstractHandlerMethodAdapter.supports() @Override public final boolean supports(Object handler) { return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); }
This method only determines whether the handler is a HandlerMethod. Moreover, considering that its subclass may have other restrictions on the methods that can be processed, it also calls the supportsInternal method of the subclass, which can be used by the subclass to determine whether it supports the processing of the corresponding HandlerMethod.
The handle method in the AbstractHandlerMethodAdapter is also extremely simple. It directly calls the handleInternal method of the subclass, which belongs to the template design mode:
// AbstractHandlerMethodAdapter.handle() @Override @Nullable public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); }
Let's take a look at requestmappinghandleradatper How does handleinternal write:
@Override 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) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } // ... Omit return mav; }
It's a little long. To sum up, do different processing according to different situations, then use invokeHandlerMethod to call the specific HandlerMethod, and finally return to ModelAndView.
Why not call HandlerMethod directly here, but wrap a layer of methods? You know, those Controller methods you write do not necessarily return ModelAndView. Converting the things returned by those methods into ModelAndView according to the situation is the reason for the existence of this invokeHandlerMethod method.
I won't read the specific code. I think it's enough to satisfy my curiosity.
summary
Look at the following figure and summarize it by yourself. Note that starting from the view parser, it is not the scope of this article.
My summary
- DispatcherServlet receives the front-end request and finds a HandlerMapping for processing the request
- HandlerMapping returns a HandlerExecutionChain, which contains a stack of interceptors and a Handler object of any type
- Execute the Interceptor's preHandle method
- In order to adapt any type of Handler to SpringMVC, you need to provide a HandlerAdapter for each Handler
- Execute the supports method of all handleradapters to find the adapter that supports this kind of Handler
- Execute the handle method of the found HandlerAdapter, process the request, and return ModelAndView
- Execute the Interceptor's postHandle method
- If everything is normal, end, return
- If an exception occurs, record the exception and return