编程知识 cdmana.com

Spring MVC source code analysis

Powerful DispatcherServlet

Remember when web.xml Configured in DispatcherServlet Do you ? In fact, that is SpringMVC The entrance to the frame , This is also struts2 and springmvc One of the differences ,struts2 It's through filter Of , and springmvc It's through servlet Of . look down servlet Structure diagram
 chart
 Class diagram

From the picture above, it is obvious that DispatcherServlet and Servlet as well as Spring The relationship between . And our focus today is on DispatchServlet Speaking of .

I use SpringBoot Set up a very simple background project , Used to analyze . The code is as follows

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
@AllArgsConstructor
public class User {

private Integer id;

private String name;

private Integer age;

private String address;

public User() {

}
}

/**
* @author generalthink
*/
@RestController
@RequestMapping("/user")
public class UserController {

@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public User getUser(HttpServletRequest request,@PathVariable Integer id) {

// Create a user, Don't go to the database just for analysis springmvc Source code 
User user = User.builder()
.id(id)
.age(ThreadLocalRandom.current().nextInt(30))
.name("zzz" + id)
.address(" Chengdu ").build();

return user;
}

@RequestMapping(value = "/condition",method = RequestMethod.GET)
public User getByNameOrAge(@RequestParam String name,@RequestParam Integer age) {
User user = User.builder().name(name).age(age).address(" Chengdu ").id(2).build();
return user;
}

@PostMapping
public Integer saveUser(@RequestBody User user) {

Integer id = user.getName().hashCode() - user.getAge().hashCode();

return id > 0 ? id : -id;
}

}

In order to facilitate debugging, we focus more on SpringMVC Source code , So the data here are all fake . And the focus here is on using annotations Controller(org.springframework.stereotype.Controller), instead of Controller Interface (org.springframework.web.servlet.mvc.Controller), The difference between the two is mainly concerned with the annotation only , One needs to implement the interface , But they all perform the basic function of processing requests . We all know that visiting servlet The default is to visit service Methodical , So we hit the breakpoint at HttpServlet Of service In the method , At this point, you can view the entire call stack as follows
 The call stack
From here we also know how to ask from servlet here we are DispatcherServlet Of , So let's see DispatcherServlet Of doDiapatch The method logic of , Here's the core logic , Remove some other non core logic

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        
        // Notice what's put back here is HandlerExecutionChain object 
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        
            ModelAndView mv = null;
            Exception dispatchException = null;

            // Check for file uploads 
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            //  Based on the current request obtain handler,handler Contains the request url, And the final positioning of controller as well as controller The method in 
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            //  adopt handler Get the corresponding adapter , Mainly complete the parameter analysis 
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

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

            //  call Controller The method in 
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        
    }

You can see that the core logic is actually very simple , First check if it's multipart request, If it is, then for the current request Do some packaging ( Extract files, etc ), Then get the corresponding handler( Saved the request url Corresponding controller as well as method And a series of Interceptor), And then through handler Get the corresponding handlerAdapter( Parameter assembly ), It is used to call the final method

analysis multipart

So how to resolve that the current request is a file upload request ? Here you go straight to checkMultipart Method to see how to parse :

// I simplified the code , Only the core logic is extracted 
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {        
        return this.multipartResolver.resolveMultipart(request);    
    }
    return request;
}

It can be seen from here that through multipartResolver Determine whether the current request is a file upload request , If so, return MultipartHttpServletRequest( Inherited from HttpServletRequest). If not, go back to the original request object .
So here comes the question multipartResolver When was it initialized ?

We are idea You can directly locate the breakpoint to multipartResolver Attribute , When you request access, you will find that the breakpoint directly enters initMultipartResolver In the method , Then trace the entire call stack , You can see that the call relationship is as follows :
 initialization multipartResovler
The graph shows that it is initializing servlet The time is right multipartResolver Initialized .

private void initMultipartResolver(ApplicationContext context) {

// from Spring In order to get id by multipartResolver Class 
    this.multipartResolver = context.getBean("multipartResolver", MultipartResolver.class);
}

MultipartResolver Interface with CommonsMultipartResolve as well as StandardServletMultipartResolver2 Kind of implementation ,CommonsMultipartResolver Interfaces depend on commons-upload Component implemented , and StandardServletMultipartResolver It depends on Servlet Of part(servlet3 Just exist ) Realized . Both methods determine whether it is a file upload request isMultipart Whether the request method is post as well as content-type Does the head contain multipart/ To judge .

DispatchServlet What is initialized

protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);  // initialization multipartResolver
   initLocaleResolver(context);// initialization localeResolver
   initThemeResolver(context);// initialization themResolver
   initHandlerMappings(context);// initialization handerMappings
   initHandlerAdapters(context);// initialization handlerAdapters
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);// Initialize attempt parser 
   initFlashMapManager(context);
}

These initialization contents will be used one by one later , Here's an impression .

Obtain on request mapperHandler

Or into getHander To see what's done ?

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
    for (HandlerMapping hm : this.handlerMappings) {
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
            return handler;
            }
        }
    }
    return null;
}

according to HandlerMapping To see the corresponding handler, So go into initHandlerMappings Method to see how to initialize handlerMappings
 initialization handlerMappings

Which gets the default handlerMappings Yes spring-webmvc Of org.springframework.web.servlet Medium DispatcherServlet.properties Search for , The content of the file is as follows
DispatcherServlet.properties
because detechAllhanderMappings The default is true, So you get all the HanderMapping Implementation class of , Let's take a look at its class diagram structure
HandlerMapping Class diagram
this.handlerMappings Value
These are a few HandlerMapping Its function is as follows :
SimpleUrlHandlerMapping : Allow to specify explicitly URL Patterns and Handler The mapping relation of , One was maintained internally urlMap To make sure url and handler The relationship between
BeanNameUrlHandlerMapping: Appoint URL and bean The mapping of names , Not commonly used , Our focus is also mainly on RequestMappingHandlerMapping in

It's basically clear here HandlerMapping The role of : help DispatcherServlet Conduct Web Requested URL Matching to concrete classes , It's called HandlerMapping Because in SpringMVC China is not limited to
Must be annotated Controller We can also inherit Controller Interface , You can also use third-party interfaces , such as Struts2 Medium Action
RequestMappingHandlerMapping
Then take a look getHandler The implementation of the :

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
      for (HandlerMapping hm : this.handlerMappings) {
         HandlerExecutionChain handler = hm.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}

Back to handler yes HandlerExecutionChain, It contains the real handler And the blocker , It can be done before execution , After execution , Execution completes these three phases to process business logic .
RequestMappingHandlerMapping Of getHandler The call logic of is as follows :
 Call logic

Will traverse all Controller Of url Check whether there are qualified match(head,url,produce,consume,method To meet the requirements ), use antMatcher In the way of url matching , If it matches, it returns the corresponding handler, Otherwise return to null, If the mapping finds duplicate mappings (url The mapping is the same , The request method is the same , Parameters are the same , The request header is the same ,consume identical ,produce identical , The custom parameters are the same ), An exception will be thrown .

and SimpleUrlHandlerMapping The call logic of is as follows :
SimpleUrlHandlerMapping Call logic
It maintains url To handler Mapping , Through the first url To urlMap Find the corresponding handler, If not, try pattenMatch, If you succeed, you will return the corresponding handler, Return if not matched null.

We'll find out how to deal with it HandlerMapping Here we use the template method , Business logic is defined in the abstract class , The specific implementation only needs to realize its own business logic . At the same time, it also conforms to the principle of opening and closing , It's all interface oriented programming , I can't help but admire the logic involved here .

By the time we get here, we'll find out what we defined earlier Controller It's obviously in line with RequestMappingHandlerMapping Of strategy , So back HandlerExecutionChain It already contains the full path of the method to be accessed .

About HandlerAdapter

HandlerMapping Will pass HandlerExecutionChain Return to one Object Type of Handler object , be used for Web Request processing , there Handler There's no limit to what type , Generally speaking, any type of Handler Can be in
SpringMVC Use in , As long as it's used to process Web The processing object of the request is OK .

But for the DispatcherServlet There is a problem , It can't tell what kind of Handler, You can't tell if it's calling Handler Which method of processing the request , To call various types of Handler,
DispatcherServlet Will be different Handler The call responsibility for the HandlerAdapter Role .

Have a look first HandlerAdpter Definition of interface

public interface HandlerAdapter {

boolean supports(Object handler);


@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;


long getLastModified(HttpServletRequest request, Object handler);
}

Main concern supports and handle Method . Let's take a look at DispatcherServlet in handlerAdapters The initialization process , and handlerMappings The initialization process is similar to
 initialization HandlerAdapters
And then we have a look at HandlerAdapter Class relation of
HandlerAdapter Class diagram
alike , Still look for the right strategy for Adapter, Our main concern is RequestMappingHandlerAdapter( The others are rarely used ), So this is mainly about it . View it support Implementation code :
supports Method
Above about handler It's actually stated in the statement Object handler It's actually HandlerMethod, So here's the corresponding HandlerAdapter Namely RequestMappingHandlerAdapter.

After finding the corresponding adapter , Now you can call the real logic . Before that, users can do something with interceptors , Like logging , Print execution time, etc , So if you want to add a statement before the executed method , We just need to configure our own blocker .
 Execute interceptor method
Next we focus on handle Method , See what it's going to do ?, Have a look first handle Method execution flow , alike adapter The template method is also used , First define the process in the parent class , Subclasses only need to implement logic , So the first call here is AbstracthandlerMethodAdapter Of invokeHadlerMethod Method , Among them the HandlerMethod It was packaged .
invokeHandle
invokeAndHandle
We got to the first step , have a look invokeForRequest What is the main part of the method
invokeForRequest

Find that the calling logic of this method is actually very simple , It's parsing parameters , And then call the method . Let's take a look at how to parse the parameters ?
 Argument parsing
You can see almost all the logic in the core argumentResovlers In the middle , So supportive arguementResolver What are they? ? Where is it initialized ?

First of all, you need to locate where this attribute comes from ,RequestMappingHandlerAdapter Realized InitializingBean, It will be executed during initialization afterPropertiesSet Method , In the middle of it, to arguementResolvers as well as returnValueHandlers It's initialized .
Different resovler The supported parameter parsing is different , For example, there is support HttpServletRequest Injected , Have a support HttpServletREsponse There is also support body Body injection and so on .
arguementResovler initialization

returnValueHandlers initialization
After parameter analysis, we get the data needed by reflection ,class,method And parameters , Finally through java Reflection of api Call .
 Reflection calls real methods

thus ,springmvc The whole process of calling is basically clear .
But here the problem is not over , Because we don't know how to parse the parameters . such as get How to submit data ?post How to submit data ? How to convert to object ? This writing problem still exists , Let's continue to study .
Here I use postman Tools to initiate requests , First visit Get http://localhost:8080/user/condition?name=zhangsan&age=25, Locate the resolveArgument Method
 How to get specific arguementResolver

And then it was executed revolver.resolveArgument Method , The same template method is used here , In the abstract class AbstractNamedValueMethodArgumentResolver Define the process in , Each subclass only needs to implement its own logic .RequestParamMethodArgumentResolver The parameter of is through request.getParameter To get it . After getting the parameters, the reflection call is executed , At this time, we implemented what we wrote UserController The corresponding method of , Got it User object , The next step is to process the return value , adopt returnValueHandlers To deal with
 Process return value

handler The data will be processed according to the type returned , For example, it's through here response Output data to the requester , The output data is also through messageConverter To achieve
 Processing data output
Finally get ModalAndView object , But here because there is no modalAndView So back null. Last in DispatcherServlet Of processDispatchResult The call logic of the method is as follows
 The final treatment

How to resolve such requests ?

@PostMapping
public Integer saveUser(@RequestBody User user) {

Integer id = user.getName().hashCode() - user.getAge().hashCode();

return id > 0 ? id : -id;
}

Again, we focus on parsing parameters , In the last get In the example of the request, I said that I would visit AbstractNamedValueMethodArgumentResolver, But it's being dealt with @RequestBody It uses RequestResponseBodyMethodProcessor, It reproduces resolveArgument Method . So it doesn't execute the logic of the parent class .

 Argument parsing

Finally, it will be located here jakson Of objectMapper in , stay spring boot in , By default Jackson To achieve java Object to json Serialization and deserialization of formats . Of course, it can be configured messageConvert Of , Just implement Spring Of HttpMessageConverter that will do .

This is the end of source code analysis , Of course, there are still some things not mentioned , such as View The rendering of , The general view is diverse , also html,xml,jsp wait , therefore springmvc It also provides an interface for users to choose the template they need , Just implement ViewResolver Interface can . Also about Theme,MessageResource,Exception And so on , It's too long to talk about , I believe that it will be very simple to master the core process and see other processing , So there is no analysis of other details here .

A picture is worth a thousand words

SpringMVC flow chart

版权声明
本文为[think123]所创,转载请带上原文链接,感谢

Scroll to Top