preface

Only a bald head can be strong.

Star: github.com/ZhongFuChen…

This SpringMVC has been pushed for a long time. I am very, very busy these days because of the integration of the system. Early return to the company liver article this weekend.

If you pay attention to Three crooked students will find that three crooked recently wrote a lot of articles are combined with the existing system to write. These problems are real development scenarios will encounter, use, these cases should not work to help students should be quite big.

So without BB, let’s get to the point of the day: “SpringMVC.”

Let’s talk a little bit about SpringMVC

If you play Zhihu, you will probably see me. I often go to Zhihu Water to answer. In Zhihu, there is a question many beginners ask: “What foundation do I need to learn SpringMVC?”

I’ll make sure they learn servlets first and then SpringMVC. Although we hardly ever write native Servlet code in real world development, I still think learning SpringMVC after learning servlets is good for understanding SpringMVC.

Before I started learning SpringMVC, I had already been exposed to another Web framework (as well as servlets, of course), the “famous” Struts2. SpringMVC has all the functionality that Struts2 has.

When I first learned Struts2, I used XML configuration to develop it. When I switched to SpringMVC annotations, I felt that SpringMVC really smelled good.

Struts2 will no longer need to learn SpringMVC in 2020, the foundation of learning SpringMVC is Servlet, as long as the Servlet foundation is good, it should not be a problem to learn SpringMVC.

From servlets to SpringMVC, you’ll find that SpringMVC does a lot of things for us, and we definitely don’t have as much code as we used to.

The Servlet:

Previously, we might have needed to manually encapsulate the passed parameters into a Bean and continue:

SpringMVC:

Now SpringMVC automatically wraps the parameter into a Bean for us

Servlet:

Previously we had to import another JAR package to handle file upload details manually:

For SpringMVC:

The SpringMVC upload file is now wrapped with a MultipartFile object

.

To put it bluntly, we could do all of these things back in the Servlet era, but SpringMVC blocks a lot of them out and makes them much more comfortable to use.

Learning SpringMVC is actually learning how to use these features, so it shouldn’t be too difficult. The SpringMVC ebook is actually about how SpringMVC is used

  • For example, if you pass a date string, SpringMVC doesn’t convert it to a date by default, how can you do that?
  • How are SpringMVC file uploads used
  • How is the SpringMVC interceptor used
  • How does SpringMVC bind parameters
  • .

Now the “e-book” is out, but don’t worry, the big story is coming. Obviously, you can see how SpringMVC is used from the above ebook.

But you’re not going to be asked about SpringMVC in an interview, and most of the interview questions are: How does SpringMVC request processing flow?

In fact, it is very simple, the flow is the following diagram:

Simplifying a bit further, you can see that the process is not complicated

You may even be able to speak in one sentence during an interview, but is that enough? Is that what the interviewer wants? Certainly not. So do we want to know what SpringMVC does? Think about it (whether you want to or not, you want to see it).

In order to make the main flow clearer, I will add some comments in the source code and delete some of the code

We’ll focus on the @responseBody and @requestBody Controller codes, which are used most often in online environments

DispatcherServlet source

First we look at the class structure of DispatcherServlet, we can clearly find that the actual DispatcherServlet is a subclass of Servlet interface (this is why so many people online say that the principle of DispatcherServlet is actually a Servlet).

We see a lot of familiar member variables (components) in the DispatcherServlet class, so it looks like DispatcherServlet has everything we need.

// File handler
private MultipartResolver multipartResolver;

/ / the mapping
private List<HandlerMapping> handlerMappings;

/ / adapter
private List<HandlerAdapter> handlerAdapters;

// Exception handler
private List<HandlerExceptionResolver> handlerExceptionResolvers;

// View resolver
private List<ViewResolver> viewResolvers;
Copy the code

We then find that they are initialized on initStrategies() :

protected void initStrategies(ApplicationContext context) {
  initMultipartResolver(context);
  initLocaleResolver(context);
  initThemeResolver(context);
  initHandlerMappings(context);
  initHandlerAdapters(context);
  initHandlerExceptionResolvers(context);
  initRequestToViewNameTranslator(context);
  initViewResolvers(context);
  initFlashMapManager(context);
}
Copy the code

Requests come into the DispatcherServlet and all of them are actually called to the doService() method. Let’s see what the doService() method does:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		// Set some context... (omitting a large part)
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

		try {
      / / call doDispatch
			doDispatch(request, response);
		}
		finally {
			if(! WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {if(attributesSnapshot ! =null) { restoreAttributesAfterInclude(request, attributesSnapshot); }}}}Copy the code

So the request goes to doDispatch(Request, response); Inside, let’s go in again:

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 {
         // Check if it is a file upload requestprocessedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest ! = request);/ / find HandlerExecutionChain
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null || mappedHandler.getHandler() == null) {
            noHandlerFound(processedRequest, response);
            return;
         }
         // Get the corresponding Hanlder adapter
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

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

         // Actually process the request
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         // View resolver processing
         applyDefaultViewName(processedRequest, mv);
        
         // intercept post-processing
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch(Exception ex) { dispatchException = ex; }}}Copy the code

The flow here is almost the same as the flow in our diagram above. As we know from the source code, the original SpringMVC interceptor returns a HandlerExecutionChain object in MappingHandler. This object is also not difficult, let’s see:

public class HandlerExecutionChain {

	private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

  // Real handler
	private final Object handler;

  // Interceptor List
	private HandlerInterceptor[] interceptors;
	private List<HandlerInterceptor> interceptorList;

	private int interceptorIndex = -1;
}
Copy the code

OK, we have seen the whole process, by the way, shall we go to see how it finds handler? ** three crooked with you rush! ** When we click on getHandler(), it iterates through the default Handler and selects the appropriate one:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// Iterate through the default Handler instance and return the appropriate one
  for (HandlerMapping hm : this.handlerMappings) {
    HandlerExecutionChain handler = hm.getHandler(request);
    if(handler ! =null) {
      returnhandler; }}return null;
}
Copy the code

So let’s go inside the getHandler, and there’s a couple more layers, and we’ll see that it matches by path, and it goes to the lookupHandlerMethod

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<Match>();
  	// Get the path
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);

  	// Sort the matches to find the best match
		if(! matches.isEmpty()) { Comparator<Match> comparator =new MatchComparator(getMappingComparator(request));
			Collections.sort(matches, comparator);
			if (logger.isTraceEnabled()) {
				logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
						lookupPath + "]." + matches);
			}
			Match bestMatch = matches.get(0);
			if (matches.size() > 1) {
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
							request.getRequestURL() + "' : {" + m1 + "," + m2 + "}");
				}
			}
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); }}Copy the code

Finding the interceptor is probably one of the steps above, so we can get the HandlerExecutionChain, and once we find the HandlerExecutionChain, we go to the corresponding HandlerAdaptor first. Let’s also go and see what’s inside:

// Iterate through the HandlerAdapter instance to find an appropriate return
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (ha.supports(handler)) {
				returnha; }}}Copy the code

We see a common RequestMappingHandlerAdapter HandlerAdapter instance, will find that he going to make a lot of initialization parameters parser, in fact, we often use the @ ResponseBody parser is built into the inside:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers(a) {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();

		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false));
  	// ResponseBody Requestbody parser
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), t
		/ / etc.
		return resolvers;
	}
Copy the code

After getting the HandlerAdaptor, the interceptor preprocessing is followed by the actual mv = ha.handle(processedRequest, Response, mappedHandler.gethandler ()).

This nested several layers, and I will not post code one by one, we will enter the ServletInvocableHandlerMethod# invokeAndHandle method, what we do have a look at it:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

  	// Process the request
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);

		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
				mavContainer.setRequestHandled(true);
				return; }}/ /..

		mavContainer.setRequestHandled(false);
		try {
      // Process the return value
			this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); }}Copy the code

The way to handle a request let’s go in and look at invokeForRequest

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
		
  	// Get parameters
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		
  	// Call the method
		Object returnValue = doInvoke(args);
		if (logger.isTraceEnabled()) {
			logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
		}
		return returnValue;
	}
Copy the code

Let’s see how it handles arguments. The getMethodArgumentValues method goes in:

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

  	// Get parameters
		MethodParameter[] parameters = getMethodParameters();
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
			args[i] = resolveProvidedArgument(parameter, providedArgs);
			if(args[i] ! =null) {
				continue;
			}
      // Find the appropriate parameter parser
			if (this.argumentResolvers.supportsParameter(parameter)) {
				try {
					args[i] = this.argumentResolvers.resolveArgument(
							parameter, mavContainer, request, this.dataBinderFactory);
					continue;
				}
				/ /...
		}
		return args;
	}
Copy the code

These parameter parsers are actually built into the HandlerAdaptor, and it’s hard to put code here, so I’ll take a screenshot:

For RequestResponseBodyMethodProcessor parser what did we see inside:

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

    // Convert arguments by Converters
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
		// ...
		mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

		return arg;
	}
Copy the code

Then go in to see inside the readWithMessageConverters:

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter param, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		/ /... Processing request headers

		try {
			inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);

      // HttpMessageConverter instance to convert parameters
			for(HttpMessageConverter<? > converter :this.messageConverters) { Class<HttpMessageConverter<? >> converterType = (Class<HttpMessageConverter<? >>) converter.getClass();if (converter instanceofGenericHttpMessageConverter) { GenericHttpMessageConverter<? > genericConverter = (GenericHttpMessageConverter<? >) converter;if (genericConverter.canRead(targetType, contextClass, contentType)) {
						if (logger.isDebugEnabled()) {
							logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
						}
						if(inputMessage.getBody() ! =null) {
							inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
							body = genericConverter.read(targetType, contextClass, inputMessage);
							body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
						}
						else {
							body = null;
							body = getAdvice().handleEmptyBody(body, inputMessage, param, targetType, converterType);
						}
						break; }}/ /... All kinds of judgment
		

		return body;
	}
Copy the code

See here, do not understand, want to quit the feeling? Don’t panic, here’s a look at this familiar configuration:

<! -- Start JSON return format -->
	<bean	class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
		<property name="messageConverters">
			<list>
				<ref bean="jacksonMessageConverter" />
			</list>
		</property>
	</bean>
	<bean id="jacksonMessageConverter"
		class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
		<property name="supportedMediaTypes">
			<list>
				<value>text/html; charset=UTF-8</value>
				<value>application/json; charset=UTF-8</value>
				<value>application/x-www-form-urlencoded; charset=UTF-8</value>
			</list>
		</property>
		<property name="objectMapper" ref="jacksonObjectMapper" />
	</bean>
	<bean id="jacksonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />

Copy the code

We want in for SpringMVC using the @ ResponseBody return JSON format will be configured on the configuration of the above, in the configuration file RequestMappingHandlerAdapter this adapter is said above that, Built-in RequestResponseBodyMethodProcessor parser, then MappingJackson2HttpMessageConverter is actually HttpMessageConverter interface instance

In return, the HttpMessageConverter converts the parameters to an HTTP response message. The transformation process is roughly as shown in the figure:

View parser behind will not paste, the general process is the above source code, I draw a picture to deepen the understanding of it:

The last

SpringMVC is very simple when we use it, it actually helps us do a lot internally (there are various HandlerAdaptor), SpringMVC request process interview is still a lot of face, or you can look at the source code it helps us do what, after a review may find that they can understand the previous configuration.

References:

  • www.cnblogs.com/java-chen-h…
  • www.jianshu.com/p/1bff57c74…
  • Stackoverflow.com/questions/1…

Summary of various knowledge points

  • The 92 – page Mybatis
  • Multithreading on page 129
  • 141 pages of the Servlet
  • The JSP page 158
  • A collection of 76 pages
  • 64 pages of JDBC
  • 105 pages of data structures and algorithms
  • The Spring of 142 pages
  • Filters and listeners on page 58
  • 30 pages of HTTP
  • Hibernate
  • AJAX
  • Redis
  • .

Open source project (8K+ STAR) :

  • GitHub
  • Gitee access is faster

If you want to follow my updated articles and shared dry goods in real time, you can search Java3y on wechat.