Emerging Learning Spring MVC

Preface

Only a bare head can be strong.

Text has been included in my GitHub featured article. Welcome to Star: https://github.com/ZhongFuCheng3y/3y

This Spring MVC has been pushed for a long time and has been very, very busy this time because of the integration of the system.This weekend I returned to the article Corporate Liver earlier.

If you pay attention to the synergy of Santiao, you will find that many articles recently written by Santiao are written in combination with the existing system.These are all problems that real development scenarios will encounter and use, and these cases should be magnificent to help the students who are not working.

No more BB s, let's get to today's topic, "Spring MVC"

Start with a simple chat about Spring MVC

If you know it, you'll probably see me.I often go for a water answer.Knowing that there is a question many beginners will ask: "What basics do I need to learn Spring MVC?"

I'm sure they'll learn Servlet before Spring MVC.Although we hardly write native Servlet code in real-world development, I always believe that learning Spring MVC after Servlet is good for understanding Spring MVC.

Three-sided off-topic topic: I actually had access to another web framework before I learned Spring MVC (and Servlet did, of course), Struts2, which is "Famous".SpringMVC will have all the features that Struts2 has.

When you first learned Struts2, you developed it in the way of XML configuration, and then moved to the Spring MVC annotations, you felt that Spring MVC really smells good.

Struts2 is no longer required in 2020. The foundation of learning Spring MVC is Servlet. As long as the Servlet foundation is still good, starting Spring MVC should not be a problem.

From Servlet to Spring MVC, you'll find that Spring MVC has done a lot for us, and our code is certainly not as much as it used to be.

Servlet:

We might need to manually encapsulate the parameters passed in as a Bean before continuing to download:

SpringMVC:

Now SpringMVC automatically wraps the parameters into a Bean for us

Servlet:

Previously, we imported other jar packages to handle the details of file uploads manually:

SpringMVC:

Now SpringMVC upload files are encapsulated for us with a MultipartFile object

........

To put it plainly, we were able to do all this work during Servlet, but SpringMVC blocked a lot of things, so we were more comfortable with it.

It's not too difficult to actually learn how these functions work when learning SpringMVC.This compilation of Spring MVC ebooks is actually about how Spring MVC is used

  • For example, by passing a date string, SpringMVC cannot convert to a date by default, so what can we do?
  • How to use SpringMVC file upload
  • How the Interceptor for SpringMVC works
  • How SpringMVC binds parameters
  • ......

Now the E-Book is out, but don't worry, the main thing is in the back.Obviously, you can see how SpringMVC works from the e-book above.

However, you won't be asked some of the usages of Spring MVC during the interview, and what Spring MVC interviews ask most about is how the process of processing Spring MVC requests works.

In fact, it is also very simple, the process is the following picture:

A little simpler, you can see that the process is not complex

You can even finish a sentence during an interview, but is that enough? Is this what the interviewer wants?That's definitely not.So we want to know what Spring MVC is doing?Think about it (whether you want it or not, think about it three times).

To make the main process clearer, I'll add some comments to the source code and delete some of the code.

With @ResponseBody and @RequestBody's ontroller code explanations, this is the most used online environment

Dispatcher Servlet Source

First, looking at the class structure of Dispatcher Servlet, it is clear that the actual Dispatcher Servlet is a subclass of the Servlet interface (which is why so many people on the Web say that Dispatcher Servlet works by virtue of Servlet).

We see a lot of familiar member variables (components) on the Dispatcher Servlet class, so look at what we want, Dispatcher Servlets can all be there.

// File Processor
private MultipartResolver multipartResolver;

// Mapper
private List<HandlerMapping> handlerMappings;

// Adapter
private List<HandlerAdapter> handlerAdapters;

// Exception Handler
private List<HandlerExceptionResolver> handlerExceptionResolvers;

// view resolver
private List<ViewResolver> viewResolvers;

Then we will 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);
}

The request goes into the Dispatcher Servlet, and all of it actually hits the doService() method.Let's see what this doService() method does:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		// Set some context... (omit 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);
				}
			}
		}
	}

So the request goes to doDispatch; inside, let's go in and see:

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 file upload request
         processedRequest = 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;
         }

         // Really Processing Requests
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         // View Parser Processing
         applyDefaultViewName(processedRequest, mv);
        
         // Intercept Postprocessing
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
   }
}

The process here is almost identical to that in the diagram above.What we know from the source code is that the original SpringMVC interceptors returned together in MappingHandler, returning a HandlerExecutionChain object.This object is not difficult either. Let's look at it:

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;
}

OK, we've finished the whole process. By the way, would you like us to see how it finds the handler?Three Tilts with you!When we click getHandler(), we find that it iterates through the default implemented handler and choose the appropriate one:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// Traverse through the default Andler instance and choose the appropriate one to return to
  for (HandlerMapping hm : this.handlerMappings) {
    HandlerExecutionChain handler = hm.getHandler(request);
    if (handler != null) {
      return handler;
    }
  }
  return null;
}

Go in and look inside the getHandler, there are several layers inside, and we can finally see that it matches according to its path and goes to a method like lookupHandlerMethod

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

  	// Sort 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);
		}
	}

Finding an interceptor is probably the same process above, so we can get the Handler Execution Chain smoothly. After finding the Handler Execution Chain, we go to get the corresponding Handler Adaptor first.Let's also go and see what's going on inside:

// Walk through the HandlerAdapter instance to find a suitable return
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (ha.supports(handler)) {
				return ha;
			}
		}
	}

Looking at a common Handler Adapter instance, RequestMapping HandlerAdapter, you will see that it initializes many parameter parsers, but the @ResponseBody parser that we often use is built into it:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		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
		// Wait
		return resolvers;
	}

With the HandlerAdaptor, the interceptor's preprocessing followed by the actual MV =Ha.handle(processedRequest, response,MappedHandler.getHandler()).

There are several layers nested here, so I don't have to paste any code anymore. We'll go into the ServletInvocableHandlerMethod#invokeAndHandle method and see what we've done here:

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

  	// Processing 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 {
      // Processing return values
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
	}

How to handle requests Let's go in and see invokeForRequest

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

Let's see how it handles the parameters, and the getMethodArgumentValues method goes in to see:

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 parameter parser for the adapter
			if (this.argumentResolvers.supportsParameter(parameter)) {
				try {
					args[i] = this.argumentResolvers.resolveArgument(
							parameter, mavContainer, request, this.dataBinderFactory);
					continue;
				}
				//.....
		}
		return args;
	}

These parameter parsers are actually those built into the HandlerAdaptor, so it's hard to put code here, so let's take a screenshot:

For the RequestResponseBodyMethodProcessor parser, let's see what's going on inside:

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

    // Converters for parameter conversion
		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;
	}

Go inside readWithMessageConverters and see:

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

		// ...process the request header

		try {
			inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);

      // HttpMessageConverter instance to convert parameters
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				if (converter instanceof GenericHttpMessageConverter) {
					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;
					}
				}
				//...various judgments
		

		return body;
	}

See here, do you not understand, want to quit feeling??Don't panic, just take a look at this familiar configuration:

<!-- start-up 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" />

The configuration above is configured on the configuration file when SpringMVC wants to use @ResponseBody to return JSON format. RequestMappingHandlerAdapter is the same adapter mentioned above, with the RequestResponseBodyMethodProcessor parser built in, and MappingJackson2HttpMessageConverter is actually an instance of the HttpMessageConverter interface

Then, when returned, the parameters are converted by HttpMessageConverter and written to the HTTP response message.The process of conversion is roughly as shown in the diagram:

The view parser will not be pasted after, the approximate process is like the source code above, I will draw a picture to further understand it:

Last

SpringMVC is very easy to use and actually helps us do a lot internally (there are various Hadler Adaptors). We still have a lot of interviews with SpringMVC's request process, or we can see what the source code does for us. Over time, you may find that you can read the previous configuration.

Reference material:

Summary of various knowledge points

Open source project covering all Java backend knowledge points (8K+ star already):

WeChat searches Java3y if you want to keep an eye on my updated articles and shared dried goods in real time.

Tags: Java Spring JSON github

Posted on Tue, 19 May 2020 18:52:21 -0700 by Andrei