1. Use the MVC

Before exploring the source code, let’s review how SpringMVC is configured to make it easier to understand the source code.

1.1 web. XML

<servlet>
	<servlet-name>mvc-dispatcher</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<!-- 配置springMVC需要加载的配置文件
		spring-dao.xml,spring-service.xml,spring-web.xml
		Mybatis - > spring -> springmvc
	 -->
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring/spring-*.xml</param-value>
	</init-param>
</servlet>
<servlet-mapping>
	<servlet-name>mvc-dispatcher</servlet-name>
	<!-- 默认匹配所有的请求 -->
	<url-pattern>/</url-pattern>
</servlet-mapping>
Copy the code

Value is the reference and configuration of the contextConfigLocation and DispatcherServlet(which is used to intercept requests).

1.2 the spring – web. XML

<! -- Configure SpringMVC --> <! -- 1. Enable SpringMVC annotation pattern --> <! - simplify configuration: (1) automatic registration DefaultAnootationHandlerMapping, AnotationMethodHandlerAdapter (2) provide some columns: Data binding, number and date format @NumberFormat, @dateTimeFormat, XML, JSON default read-write support --> < MVC :annotation-driven /> <! -- 2. Static resource default servlet configuration (1) Added static resource processing: JS, GIF, PNG (2) allowed"/"< MVC :default-servlet-handler/> <! -- 3. Configure JSP to display ViewResolver --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
 	<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
 	<property name="prefix" value="/WEB-INF/jsp/" />
 	<property name="suffix" value=".jsp"/> </bean> <! --> <context:component-scan base-package="com.xxx.fantj.web" />
Copy the code

The value of the note is InternalResourceViewResolver, it will be in front of the ModelAndView returns to name plus prefix prefix, suffix specified in the loading suffix behind.

Main source code analysis of SpringMvc

Use a diagram from Spring in Action to better understand the execution process:

The above process can be divided into three parts:

  1. Map<url,Controller>Set up (and put in)WebApplicationContext)
  2. HttpRequestRequest interception handling of urls in requests (DispatchServlet handling)
  3. Reflection callsControllerAnd return to the view

This paper will analyze these three pieces.

1. Map establishment

When the container is initialized, the mapping between all urls and controllers is set up and stored in Map< URL,Controller>.

ApplicationObjectSupport # setApplicationContext method
/ / initialize the ApplicationContext @ Override public void initApplicationContext () throws ApplicationContextException { super.initApplicationContext(); detectHandlers(); }Copy the code
AbstractDetectingUrlHandlerMapping # detectHandlers () method:
/** * Register all handlers found in ApplicationContextin the current ApplicationContext.
 * <p>The actual URL determination for a handler is up to the concrete
 * {@link #determineUrlsForHandler(String)} implementation. A bean for
 * which no such URLs could be determined is simply not considered a handler.
 * @throws org.springframework.beans.BeansException if the handler couldn't be registered * @see #determineUrlsForHandler(String) */ protected void detectHandlers() throws BeansException { if (logger.isDebugEnabled()) { logger.debug("Looking for URL mappings in application context: " + getApplicationContext()); } / / to get in a container beanNames String [] beanNames = (enclosing detectHandlersInAncestorContexts? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : getApplicationContext().getBeanNamesForType(Object.class)); // Take any bean name that we can determine URLs for. For (String beanName: String[] urls = determineUrlsForHandler(beanName); // Get the bean URL (class URL + method URL) String[] urls = determineUrlsForHandler(beanName); if (! ObjectUtils.isEmpty(urls)) { // URL paths found: Let'// Save the url to beanName registerHandler(urls, beanName); }else {
			if (logger.isDebugEnabled()) {
				logger.debug("Rejected bean name '" + beanName + "': no URL paths identified"); }}}}Copy the code
DetermineUrlsForHandler () method:

The method in different subclass implementation, I here is the analysis of the DefaultAnnotationHandlerMapping implementation of a class, the class is mainly responsible for handling the @ RequestMapping annotations in the form of a statement.

/** * get the url in the @requestmaping annotation * Checksforpresence of the {@link org.springframework.web.bind.annotation.RequestMapping} * annotation on the handler class and on any of its methods. */ @Override protected String[] determineUrlsForHandler(String beanName) { ApplicationContext context = getApplicationContext(); Class<? > handlerType = context.getType(beanName); / / get beanName requestMapping requestMapping mapping = context. FindAnnotationOnBean (beanName, requestMapping. Class);if(mapping ! = null) {// The class has a @requestMapping annotation this.cachedmappings. Put (handlerType, mapping); Set<String> urls = new LinkedHashSet<String>(); // Mapping.value () = @requestMapping value String[]typeLevelPatterns = mapping.value();
		if (typeLevelPatterns. Length > 0) {// Get @requestMapping String[] methodLevelPatterns = on Controller methods determineUrlsForHandlerMethods(handlerType);for (String typeLevelPattern : typeLevelPatterns) {
				if (!typeLevelPattern.startsWith("/")) {
					typeLevelPattern = "/" + typeLevelPattern;
				}
				for(String methodLevelPattern : MethodLevelPatterns) {// Controller mapping URL + method mapping URL String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern); / / save tosetAddUrlsForPath (urls, combinedPattern) in the collection; } addUrlsForPath(urls,typeLevelPattern); } // Return all urls on the controller as an arrayreturn StringUtils.toStringArray(urls);
		}
		else{// @requestMapping mapping URL on controller is empty string, find method mapping URL directlyreturndetermineUrlsForHandlerMethods(handlerType); }} // There is no @requestMapping annotation on controllerelse if(AnnotationUtils.findAnnotation(handlerType, Controller.class) ! = null) {// Get the mapping URL on the controller methodreturn determineUrlsForHandlerMethods(handlerType);
	}
	else {
		returnnull; }}Copy the code

The deeper details of the code are easier to follow if you are interested.

At this point, the Controller and Url mapping is assembled and the request processing is analyzed.

2. Url request processing

We have configured the DispatcherServlet as the dispatcher in XML, so let’s look at its code. It is a Servlet by name, so its core method is doService().

DispatcherServlet #doService():
/** * exposes dispatcherServlet-specific request attributes and delegates to {@link#doDispatch} for the actual dispatch.
 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
 * for the actual dispatching.
 */
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	if (logger.isDebugEnabled()) {
		String requestUri = new UrlPathHelper().getRequestUri(request);
		logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +
				" request for [" + requestUri + "]"); } // Keep a snapshot of the request attributes with the request included, and restore the original attributes after include. Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {
		logger.debug("Taking snapshot of request attributes before include");
		attributesSnapshot = new HashMap<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 request object available to handler and view processing using 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()); try {doDispatch(request, response); } finally {// If not empty, restore the original property snapshot.if(attributesSnapshot ! = null) { restoreAttributesAfterInclude(request, attributesSnapshot); }}}Copy the code

As you can see, after it gets the request, it mainly sets objects to the request for subsequent processing (Handler processing and View processing). For example, the WebApplicationContext contains the controller url mapping we did in the first step.

DispatchServlet # doDispatch()
* Process the actual dispatching to the handler. * <p>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.
 * <p>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; int interceptorIndex = -1; try { ModelAndView mv; boolean errorView = false; // 1. Check whether the file is uploaded processedRequest = checkMultipart(request); // Determine handler for the current request. // 2. Handlers and Interceptors mappedHandler = getHandler(processedRequest, false); If (mappedHandler = = null | | mappedHandler. GetHandler () = = null) {/ / return 404 noHandlerFound (processedRequest, response); return; } // Apply preHandle methods of registered interceptors  = mappedHandler.getInterceptors(); if (interceptors ! = null) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (! interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); return; } interceptorIndex = i; } } // Actually invoke the handler. // 3. HandlerAdapter ha = getHandlerAdapter(mappedHandler.gethandler ()); Mv = ha.handle(processedRequest, Response, mappedHandler.gethandler ()); // Do we need view name translation? if (mv ! = null && ! mv.hasView()) { mv.setViewName(getDefaultViewName(request)); } // If (interceptors! = null) { for (int i = interceptors.length - 1; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; PostHandle (processedRequest, response, mappedHandler.gethandler (), mv); } } } catch (ModelAndViewDefiningException ex) { logger.debug("ModelAndViewDefiningException encountered", ex); mv = ex.getModelAndView(); } catch (Exception ex) { Object handler = (mappedHandler ! = null ? mappedHandler.getHandler() : null); mv = processHandlerException(processedRequest, response, handler, ex); errorView = (mv ! = null); } // Did the handler return a view to render? if (mv ! = null && ! mv.wasCleared()) { render(mv, processedRequest, 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"); }} // Trigger post-completion for successful outcome. TriggerAfterCompletion (mappedHandler, interceptorIndex, processedRequest, response, null); } catch (Exception ex) { // Trigger after-completion for thrown exception. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); throw ex; } catch (Error err) { ServletException ex = new NestedServletException("Handler processing failed", err); // Trigger after-completion for thrown exception. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); throw ex; } finally { // Clean up any resources used by a multipart request. if (processedRequest ! = request) { cleanupMultipart(processedRequest); }}}Copy the code

The method is mainly

  1. Obtained from the Request objectHandlerExecutionChain.HandlerExecutionChainThe interceptor object contains the interceptor and handler. If the obtained object is empty, thenoHandlerFoundThe 404 page is returned.
  2. Interceptor preprocessing, if successful proceed to 3
  3. Get the Handler Adapter Adapter
  4. The actual processor processes and returns the ModelAndView object

Here are some of the core details in this method:

DispatchServlet #doDispatch # noHandlerFound

response.sendError(HttpServletResponse.SC_NOT_FOUND);
Copy the code

The DispatchServlet #doDispatch #getHandler method actually calls the AbstractHandlerMapping #getHandler method. I post a core code:

Handler = getHandlerInternal(request); . String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); . // Return HandlerExecutionChainreturn getHandlerExecutionChain(handler, request);
Copy the code

As you can see, it first gets the Handler object from the Request, which explains why DispatchServlet #doService put the WebApplicationContext into the Request object earlier.

Finally, a HandlerExecutionChain object is returned.

3. Reflection calls the method that handles the request and returns the result view

In the source code above, the actual handler that processes and returns the ModelAndView object calls mv = ha.handle(processedRequest, Response, mappedHandler.gethandler ()); This method. The method by AnnotationMethodHandlerAdapter # handle () # invokeHandlerMethod () method.

AnnotationMethodHandlerAdapter #handle() #invokeHandlerMethod()
/* / protected ModelAndView invokeHandlerMethod(HttpServletRequest Request, HttpServletResponse response, Object handler) throws Exception { // 1. Access method parser ServletHandlerMethodResolver methodResolver = getMethodResolver (handler); / / 2. Parse the url in the request, access to handle the request Method handlerMethod = methodResolver. ResolveHandlerMethod (request); / / 3. The method invoker ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker (methodResolver); ServletWebRequest webRequest = new ServletWebRequest(request, response); ExtendedModelMap implicitModel = new BindingAwareModelMap(); / / 4. Execution method (method for parameters) Object result. = methodInvoker invokeHandlerMethod (handlerMethod, handler, webRequest, implicitModel);  / / 5. Encapsulated into the mv view ModelAndView mav. = methodInvoker getModelAndView (handlerMethod, handler getClass (), the result, implicitModel, webRequest); methodInvoker.updateModelAttributes(handler, (mav ! = null ? mav.getModel() : null), implicitModel, webRequest);return mav;
}
Copy the code

This method has two important points, which are resolveHandlerMethod and invokeHandlerMethod.

ResolveHandlerMethod method

MethodResolver. ResolveHandlerMethod (request) : access controller @ requestMapping classes and methods Value matches the URL of the request to find the method in the controller that handles the request. Eventually joining together is to realize the org. Springframework. Util. AntPathMatcher# combine methods.

InvokeHandlerMethod method

As the name suggests, it’s based on reflection, so what does it do?

Parses the parameters on the method and invokes the method.

// All of the above are in preparation for parsing method parameters... [] args = resolveHandlerArguments(handlerMethodToInvoke, Handler, webRequest, implicitModel); // The method that actually performs the parse callreturn doInvokeMethod(handlerMethodToInvoke, handler, args);
Copy the code
InvokeHandlerMethod method #resolveHandlerArguments method

The code is a little long, so LET me just outline what it does.

  • If the method has an annotation as an argument, parse the annotation to get the parameter name, then get the request parameter name, and assign if the two are consistentHandlerMethodInvoker#resolveRequestParam), then put the wrapped object into the array of args[] and return it.
  • If the method’s parameters are not annotations, then the ASM framework (which reads bytecode at the bottom) is needed to help get the parameter names, then get the parameter names in the request, then assign them if they are the same, and then put the wrapped object into the array of args[] and return it.
InvokeHandlerMethod method #doInvokeMethod method
private Object doInvokeMethod(Method Method, Object target, Object[] args) throws Exception {// Sets a Method to callable, Mainly for private methods ReflectionUtils. MakeAccessible (method); Try {// reflection callsreturn method.invoke(target, args);
	}
	catch (InvocationTargetException ex) {
		ReflectionUtils.rethrowException(ex.getTargetException());
	}
	throw new IllegalStateException("Should never get here");
}
Copy the code

At this point, you can call one of the corresponding methods of the Controller corresponding to the URL in the Request request.

Conclusion:

After watching the brain must be very confused, if you have time or need to start debugging yourself. This article is just a string of overall ideas, so the functional source code is not all analysis.

That’s the most important thing to understand.

  1. The user sends the request to the DispatcherServlet of the front-end controller
  2. The DispatcherServlet invokes the HandlerMapping processor mapper upon receiving the request.
  3. The processor mapper finds the specific processor based on the request URL, generates the processor object and a processor interceptor (if any) back to the DispatcherServlet.
  4. The DispatcherServlet invokes the handler through the HandlerAdapter processor adapter
  5. A HandlerAdapter executes a handler (also called a back-end controller).
  6. Controller completes execution and returns ModelAndView
  7. The HandlerAdapter returns the handler execution result, ModelAndView, to the DispatcherServlet
  8. The DispatcherServlet passes the ModelAndView to the ViewReslover view parser
  9. ViewReslover parses and returns the concrete View object
  10. The DispatcherServlet renders the View (i.e. populates the View with model data).
  11. DispatcherServlet responds to the user

References:

  • Github.com/fangjian042…
  • Docs. Spring. IO/spring/docs…
  • translate.google.cn/

Finally, share a wave of benefits

Summary of my Java learning process and interview knowledge