Reprint, please declare the source: https://juejin.cn/post/6844903550242258958
The first two articles directly on the SpringMVC components of the source code analysis, may be a lot of friends will feel a little confused. So today I’m going to come back and talk about the core controller of SpringMVC, and use that as a pivot to learn the whole SpringMVC body of knowledge.
How is SpringMVC used in a project?
Some configuration files for SSM projects have been described in detail in the project Development Framework -SSM article. For a Spring application, it is essential:
<context-param>
<param-name>contextConfigLocation</param-name>
<! -- <param-value>classpath*:config/applicationContext.xml</param-value> -->
<param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>
<! Configure a listener to forward requests to the Spring framework.
<! -- Spring listener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Copy the code
The ContextLoadListener is used to initialize the Spring container and load the Bean. So if we need to provide WEB functionality, we need another one, which is SpringMVC. Of course, we also need a configuration to initialize SpringMVC. HandlerMapping and SpringMVC Source Code Series: AbstractHandlerMapping is about HnadlerMapping, but not only these two, but also several important subclasses, which will be updated in the future) :
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<! XML,spring-service. XML,spring-web. XML Mybatis (if available) -> spring- > springMVC ->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<! -- Default matches all requests -->
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
Copy the code
Once we’ve configured the above in web.xml (and of course ensured that our Spring configuration and SpringMVC configuration files are all right), we can start the Web container (such as Jetty) by typing something like: http://localhost:80/myproject/index.do to access our application.
As the saying goes, the gas of knowing; So why should we be able to access our SSM project after configuring the relevant configuration files? From sending a request (http://localhost:80/myproject/index.do) that shows the final interface, in this process, the Spring to help what have we done? (Initialization of the SpringIOC container is outlined in Spring Technology Insider – Container Refresh: Wac. Refresh for your reference)
SpringMVC handles the request process
Let’s take a look at the SpringMVC request processing process. Figure 1-13 shows the process of sending a request to the interface.
DispatcherServlet
OK, let’s go straight to the DispatcherServlet class definition:
public class DispatcherServlet extends FrameworkServlet
Copy the code
DispatcherServlet inherits from FrameworkServlet, is that it?
First of all, why should there be a green department? Some of you may have already thought that the green part is not Spring’s, but Java’s own. Spring has successfully acquired a JAVA WEB pedigree through HttpServletBean (Spring was originally written in JAVA, haha). Check out my previous article on servlets, which briefly introduced this interface.
On the other hand, since DispatcherServlet is ultimately a Servlet, it must have servlet-functional behavior.
Knock on the blackboard!! Init ->service-> Destroy: load -> instantiate ->service->destroy
There is no service method in the DispatcherServlet, but there is a doService method! (What a hard quote…)
DoService is the entrance to the DispatcherServlet. Let’s look at this method:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the requested attributes in the case of include so that the original attributes can be restored after the include.
Map<String, Object> attributesSnapshot = null;
// Determine if the given request is a containment request, that is, not a top-level HTTP request coming in from outside.
/ / check whether there is a "javax.mail. Servlet. Include. Request_uri" request attributes. You can check any of the request attributes in the contain only request.
//(see isIncludeRequest below)
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = newHashMap<String, Object>(); Enumeration<? > attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); }}}// Make the framework available to handler and View objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
//FlashMap is used to store the parameters of the forward request
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if(inputFlashMap ! =null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
doDispatch(request, response);
}
finally {
if(! WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.
if(attributesSnapshot ! =null) { restoreAttributesAfterInclude(request, attributesSnapshot); }}}}Copy the code
PS: "javax.mail. Servlet. Include." request_uri is INCLUDE_REQUEST_URI_ATTRIBUTE constant values. The isIncludeRequest(request) method can be understood with a JSP instruction:
<jsp:incluede page="index.jsp"/>
Copy the code
This directive means that a page is nested within another page, so we know that the JSP is compiled into the corresponding Servlet class to run at runtime, so there is a similar functionality and invocation syntax in the Servlet, which is the requestdispatch.include () method. So what if a servlet that has been called by another servlet using the RequestDispatcher include method wants to know the context of the calling servlet? This can be obtained using the following attribute in the request attribute:
javax.servlet.include.request_uri
javax.servlet.include.context_path
javax.servlet.include.servlet_path
javax.servlet.include.path_info
javax.servlet.include.query_string
Copy the code
In doService, you can see this in the following try block:
try {
doDispatch(request, response);
}
Copy the code
DoService did not process the request directly. Secondly, it handed the request to doDispatch for specific processing. Of course, before calling doDispatch, doService does some things, such as determining whether the request is inclde, setting some request properties, etc.
Redirect parameter transfer problem supported by FlashMap
In doService, webApplicationContext, localeResolver, themeResolve, and themeSource are all flashmap-related parameters. The code is as follows:
//FlashMap is used to store the parameters of the forward request
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if(inputFlashMap ! =null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
Copy the code
FlashMap is used to transfer parameters during Redirect forwarding.
For the above problem, we can use FlashMap to pass parameters; We need to write the required parameters to OUTPUT_FLASH_MAP_ATTRIBUTE before redirect, for example:
ServletRequestAttributes SRAttributes = (ServletRequestAttributes)(RequestContextHolder.getRequestAttributes());
HttpServletRequest req = SRAttributes.getRequest();
FlashMap flashMap = (FlashMap)(req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE));
flashMap.put("myname"."glmapper_2018");
Copy the code
After the redirect handler, Spring automatically sets it into the Model. But if that’s all there is to it, isn’t it pointless to write that code every time you redirect? Spring also provides a more convenient way to use the Attributes type RedirectAttributes variable in our handler method parameters. , let’s look at some code:
@RequestMapping("/detail/{productId}") public ModelAndView detail(HttpServletRequest request,HttpServletResponse response,RedirectAttributes attributes, @pathVariable String productId) {if (stringutils.isnotBlank (productId)) {logger.info("[product details]:detail = {}",JSONObject.toJSONString(map)); mv.addObject("detail",JSONObject.toJSONString(getDetail(productId))); Mv.addobject ("title", "details "); mv.setViewName("detail.ftl"); } / / if there is no access to the productId else {attributes. AddFlashAttribute (" MSG ", "product does not exist"); attributes.addFlashAttribute("productName", productName); The attributes. AddFlashAttribute (" title ", "a bit of a problem!" ); mv.setViewName("redirect:"/error/fail.htm"); } return mv; }Copy the code
This code is an abstraction of the original business logic error returned when I did the global error handling module some time ago. Because to handle the error uniformly, it is impossible to directly return to the error interface in the specific handler. So redirect all error handling to the error/fail.htm handler method. The parameters of the Redirect are described above, so I’m not going to go into details here. Here are some simple examples and background to understand how to use redirect Attributes.
RedirectAttributes is a session that is destroyed after being used once. If the redirect is retrieved from the fail. HTM method, the parameters are lost. Continue using RedirectAttributes in fail.htm to store the parameters and pass them to the next handler.
DoDispatch method
In order to be lazy, the explanation of the redirect parameter passing problem in Spring was inserted. Back to our doDispatch method.
Purpose: Handles the actual scheduling to handler. Handler will be obtained by applying HandlerMappings to servlets in sequence. The HandlerAdapter will find the first HandlerAdapter that supports the handler class by querying the HandlerAdapter that the servlet has installed. All HTTP methods are handled by this method. It is up to the HandlerAdapter or handler itself to decide which methods are acceptable.
In fact, the most core doDispatch code is only 4 lines, let’s take a look:
- Find our handler according to the request
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
Copy the code
- Find the corresponding HandlerAdapter based on the handler
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
Copy the code
- HandlerAdapter processing handler
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
Copy the code
- Call the processDispatchResult method to handle the synthesis of the results from the above process, including of course finding the View and rendering the output to the user
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
Copy the code
Let’s take the above as the axis, take a look at its entire source code (specific code meaning in the code marked) :
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// The current request is request
HttpServletRequest processedRequest = request;
// Handler chain (handler and interceptor)
HandlerExecutionChain mappedHandler = null;
// User id multipartRequest
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
// Familiar, this is the wrapper view we return to the user
ModelAndView mv = null;
// Handle exceptions thrown during the request. This exception does not include exceptions thrown during rendering
Exception dispatchException = null;
try {
// Check if there is an upload requestprocessedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest ! = request);// Determine the appropriate handler for the current request
mappedHandler = getHandler(processedRequest);
// If no SpringMVC is found, an exception will be raised, which we often encounter when building SpringMVC applications:
//No mapping found for HTTP request with URI XXX in
//DispatcherServlet with name XXX
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Find the HandlerAdapter according to handler
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Last-Modified for GET and Head requests
// Get the request method
String method = request.getMethod();
// This method is not GET method
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return; }}// This is how the preHandle method of our SpringMVC interceptor handles it
if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
}
// Call the concrete Handler and return our MV object.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// If asynchronous processing is required, return it directly
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// If the view is empty, the default view is set according to request
applyDefaultViewName(processedRequest, mv);
// Here's how our SpringMVC interceptor's postHandle method handles it
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// Process the return result; (exception handling, page rendering, afterCompletion triggering for interceptors, etc.)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
// Determine whether to execute an asynchronous request
if (asyncManager.isConcurrentHandlingStarted()) {
// If so, execute instead of the interceptor's postHandle and afterCompletion methods
if(mappedHandler ! =null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); }}else {
// Delete the requested resource
if(multipartRequestParsed) { cleanupMultipart(processedRequest); }}}}Copy the code
Overall, doDispatch does two things:
- Handle the request
- Page rendering
DoDispatch process flow chart
That is a general content of the whole DispatcherServlet, about the initialization of the SpringMVC container, we will first be involved in the DispatcherServlet nine components after the completion of learning. So far, there have been two articles about HandlerMapping. Since we intend to comb through the whole SpringMVC architecture, we will present the nine components from interface design and subclass through source code.
SpringMVC source series: HandlerMapping
SpringMVC source code series: AbstractHandlerMapping
If you have any comments or suggestions, leave them in the comments section below, or send us an email ([email protected])! Welcome friends to communicate with us and grow up together.