Talk about spring MVC

preface

Only a bare head can make it stronger.

The text has been included in my GitHub selected articles. Welcome to Star: https://github.com/ZhongFuCheng3y/3y

This spring MVC has been urged for a long time. This time, because of the integration of the system, it is very busy. I returned to the company early this weekend.

If you pay attention to sanwai, you will find that many of sanwai's recent articles are written in combination with the existing system. These problems are encountered and used in real development scenarios. These cases should be very helpful to the students who are not working.

There are not many BB S. Let's get to the main topic of today's "spring MVC"

Let's talk about spring MVC

If you play Zhihu, you will probably see my figure. I often go to Zhihu to answer. There is a question that many beginners will ask: "what kind of foundation do I need to learn spring MVC?"

I will let them learn Servlet first, then spring MVC. Although we can hardly write the code of native Servlet in real development, I always think that it is good to understand spring MVC after learning Servlet.

Three askew extras: before learning spring MVC, I actually had contact with another web framework (of course, Servlet also learned), that is "famous" struts 2. As long as struts 2 has some functions, spring MVC will have them.

At that time, when I was learning struts 2, I used XML configuration to develop it. When I switched to spring MVC annotation, I thought spring MVC was really fragrant.

Struts2 will not need to learn spring MVC in 2020. The basis of learning spring MVC is Servlet. As long as the Servlet basis is OK, it should not be a problem to start spring MVC.

From Servlet to spring MVC, you will find that spring MVC has done a lot for us, and our code is certainly not as much as before.

Servlet:

We may need to manually encapsulate the parameters passed in as a Bean, and then continue to pass them down:

SpringMVC:

Now spring MVC automatically helps us encapsulate parameters into a Bean

Servlet:

Before, we had to import other jar packages to handle the details of file upload manually:

SpringMVC:

Now the spring MVC upload file is encapsulated with a MultipartFile object

........

Frankly speaking, we can do all these things in the Servlet period, but spring MVC blocks a lot of things, so we are more comfortable to use.

Learning spring MVC is actually learning how to use these functions, which is not too difficult. In fact, the spring MVC e-book is about how spring MVC is used

  • For example, to pass a date string, spring MVC can't be converted to date by default, so how can we do it.
  • How to use spring MVC file upload
  • How to use spring MVC's interceptor
  • How spring MVC binds parameters
  • ......

Now "e-book" has been released, but don't worry, the play is in the back. Obviously, you can know how spring MVC is used through the above ebook.

But during the interview, people won't ask you about the usage of spring MVC. What spring MVC interviews ask most is: what is the process of spring MVC request processing.

In fact, it is also very simple. The process is as follows:

Simplify it a little more and you can see that the process is not complicated

In an interview, you can even finish it in one sentence, but is that enough? Is that what the interviewer wants? That must not be. So we want to know what spring MVC has done? Think about it (whether you want to or not, anyway, sanwai wants to see it).

Because I want the main process to be clearer, I will add some comments and delete some code in the source code

It mainly explains the Controller code of @ ResponseBody and @ RequestBody, which is the most used online environment

Dispatcher servlet source code

First, let's look at the class structure of dispatcher Servlet. We can clearly find that the actual dispatcher Servlet is a subclass of the Servlet interface (that's why so many people on the Internet say that the principle of dispatcher Servlet is actually Servlet)

We can see many familiar member variables (components) on the DispatcherServlet class, so look at what we want, the DispatcherServlet can have all of them.

//File processor
private MultipartResolver multipartResolver;

//Mapper
private List<HandlerMapping> handlerMappings;

//Adapter
private List<HandlerAdapter> handlerAdapters;

//Exception handler
private List<HandlerExceptionResolver> handlerExceptionResolvers;

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

When the request goes to the dispatcher servlet, in fact, all the requests will be typed to the doService() method. Let's see what this doService() method does:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {

        //Set some context... (omit most)
        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 will go to do dispatch (request, response); inside, let's go in and have a look:

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 handler adapter
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

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

         //Real processing request
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         //View parser processing
         applyDefaultViewName(processedRequest, mv);

         //Intercept post processing
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
   }
}

The flow here is almost the same as that shown in the figure above. What we can know from the source code is that the interceptor of spring MVC returns together when MappingHandler is used, and returns a HandlerExecutionChain object. This object is 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;
}

OK, we have seen the whole process. By the way, let's see how it finds the handler? Three slants take you to rush! After we click getHandler(), we find that it will traverse the default implemented handler, and then select the appropriate one:

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

Go to getHandler again and have a look. There are several layers in it. Finally, we can see that it matches according to the path and goes to 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 the matches to find the best
        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);
        }
    }

Looking for the interceptor is probably the same as the above process, so we can get the HandlerExecutionChain smoothly. After finding the HandlerExecutionChain, we first go to get the corresponding handleradapter. We also went to see what was done inside:

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

When we look at a common HandlerAdapter instance RequestMappingHandlerAdapter, we can see that it initializes many parameter parsers. In fact, the @ ResponseBody parser 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 resolver
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), t
        //Wait a minute
        return resolvers;
    }

After getting the HandlerAdaptor, what follows is the preprocessing of the interceptor, and then the real MV= ha.handle (processedRequest, response, mappedHandler.getHandler ()).

There are several layers nested here, so I will not paste the code one by one. We will enter the servletinvocablehandlermethod ා invokeandhandle method. Let's see what we have done here:

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

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

How to handle the request let's go in and look at invokeforequest

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

      //Get the 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 deals with parameters. Go in the getMethodArgumentValues method to see:

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

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

These parameter parsers are actually built in HandlerAdaptor. It's not easy to put code here, so let me take a screenshot:

For the RequestResponseBodyMethodProcessor parser, let's see what has been done in it:

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

    //Converting parameters through 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;
    }

Then go to readWithMessageConverters:

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

        //... process 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;
                    }
                }
                //... judgments


        return body;
    }

See here, do you have a feeling that you don't understand and want to quit?? Take a look at this familiar configuration

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

If we want to use @ ResponseBody to return JSON format in spring MVC, we will configure the above configuration on the configuration file. RequestMappingHandlerAdapter is the adapter mentioned above. The RequestResponseBodyMethodProcessor parser is built in, and then mappingjackson 2HttpMessageConverter is actually an instance of HttpMessageConverter interface

Then, when returning, it will go through HttpMessageConverter to convert the parameters and write them to HTTP response message. The transformation process is roughly as shown in the figure:

img

The view parser will not be pasted. The general process is like the source code above. Let me draw a diagram to deepen our understanding

last

Spring MVC is very easy for us to use. In fact, it helps us to do a lot internally (there are various handleradapters). Spring MVC's request process interview is still very extensive. You can see what the source code helps us to do. After a while, you may find that you can understand the previous configuration.

Now that we have been working for some time, why write spring MVC

  • I am a person who pursues typesetting. If you pay attention to my classmates early, you may find that my GitHub and article navigation read.me It will be replaced frequently. current GitHub Navigation didn't work for me (it was too long), and the early articles, to be honest, didn't work very well, so I decided to start over.
  • My article will be distributed to several platforms, but no one may read the article after it is distributed, and the drawing bed is likely to hang up because of the anti-theft chain of the platform. And because a lot of readers asked me, "can you turn your article into PDF?"? "
  • I've written a lot of series level articles, and these articles will hardly change much, so they are very suitable for "persistence".

For the above reasons, I decided to summarize my series into a PDF/HTML/WORD/epub document. To be honest, it took me a lot of time to create such a document. In order to prevent whoring, I will pay attention to my official account reply "888".

Spring MVC e-book, interested students can browse a wave. 47 pages in total

reference material:

Summary of various knowledge points

The following articles have corresponding original and exquisite PDF. In the continuous update, you can come to me to urge me to update~

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

If you want to follow my updated articles and shared dry goods in real time, wechat search java 3Y.

The contents of PDF documents are all hand beaten. You can directly ask me if you don't know anything. Official account has my contact information.


It's really important for sanwai to give him a compliment!

Tags: Programming Spring github JSON Struts

Posted on Sun, 24 May 2020 22:32:06 -0400 by buzzed_man