RequestMappingHanderAdapter request processing

Entry method is handleInternal RequestMappingHanderAdapter process the request:

@Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, Throws Exception {// The final return type ModelAndView mav; // Check the request type and session support checkRequest(request); / / execution request processing if (this. SynchronizeOnSession) {/ / whether the Session synchronization HttpSession Session = request. GetSession (false); if (session ! = null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } // Set the cache expiration time if (! Response.containsheader (HEADER_CACHE_CONTROL)) {// Check if the handler has the @sessionAttributes annotation if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }Copy the code
We can see that processing the request will eventually return a ModelAndView objectCopy the code

CheckRequest method

CheckRequest method source code:

protected final void checkRequest(HttpServletRequest request) throws ServletException { // The request type of the current supportedMethods file is supportedMethods. String method = request.getmethod (); if (this.supportedMethods ! = null && ! this.supportedMethods.contains(method)) { throw new HttpRequestMethodNotSupportedException( method, StringUtils.toStringArray(this.supportedMethods)); } // If (this.requiresession && request.getSession(false) == null) {throw if (this.requiresession && request.getSession(false) == null) {// If (this.requiresession && request.getSession(false) == null) {throw new HttpSessionRequiredException("Pre-existing session required but none found"); }}Copy the code

Determine whether the request type of the current request is supported by the supportedMethods property. How is the property initialized?

private Set<String> supportedMethods;
Copy the code
SupportedMethods attribute default is empty, can be set when RequestMappingHandlerAdapter initializationCopy the code
public WebContentGenerator(boolean restrictDefaultSupportedMethods) {
    if (restrictDefaultSupportedMethods) {
        this.supportedMethods = new LinkedHashSet<String>(4);
        this.supportedMethods.add(METHOD_GET);
        this.supportedMethods.add(METHOD_HEAD);
        this.supportedMethods.add(METHOD_POST);
    }
    initAllowHeader();
}
Copy the code
If in the constructor to deliver true restrictDefaultSupportedMethods supportedMethods would only support the GET and HEAD, POST three requestCopy the code
public AbstractHandlerMethodAdapter() {
    super(false);
}
Copy the code
AbstractHandlerMethodAdapter inherited from WebContentGenerator RequestMappingHandlerAdapter superclass AbstractHandlerMothodAdapter False is passed directly, and the request type is not checked by defaultCopy the code

getSessionAttributesHandler

GetSessionAttributesHandler method source code:

private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) { Class<? > handlerType = handlerMethod.getBeanType(); SessionAttributesHandler sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType); if (sessionAttrHandler == null) { synchronized (this.sessionAttributesHandlerCache) { sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType); if (sessionAttrHandler == null) { sessionAttrHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore); this.sessionAttributesHandlerCache.put(handlerType, sessionAttrHandler); } } } return sessionAttrHandler; } / / whether there is attributeNames or attributeTypes public Boolean hasSessionAttributes () {return (this) attributeNames) size () > 0 | |  this.attributeTypes.size() > 0); }Copy the code

Is a collection of new good empty sessionAttributesHandlerCache attribute

private final Map<Class<? >, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap<Class<? >, SessionAttributesHandler>(64);Copy the code

The first thing to do is look up the SessionAttributesHandler based on the handlerType and if it’s not created and added to it, it’s using a double check lock and it’s using a lazy load

There is also a sessionAttributeStore attribute


SessionAttributeStore properties

private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
Copy the code

DefaultSessionAttributeStore SessionAttributeStore interface is realized

public class DefaultSessionAttributeStore implements SessionAttributeStore { private String attributeNamePrefix = ""; public DefaultSessionAttributeStore() { } public void setAttributeNamePrefix(String attributeNamePrefix) { this.attributeNamePrefix = attributeNamePrefix ! = null? attributeNamePrefix:""; } public void storeAttribute(WebRequest request, String attributeName, Object attributeValue) { Assert.notNull(request, "WebRequest must not be null"); Assert.notNull(attributeName, "Attribute name must not be null"); Assert.notNull(attributeValue, "Attribute value must not be null"); String storeAttributeName = this.getAttributeNameInSession(request, attributeName); request.setAttribute(storeAttributeName, attributeValue, 1); } public Object retrieveAttribute(WebRequest request, String attributeName) { Assert.notNull(request, "WebRequest must not be null"); Assert.notNull(attributeName, "Attribute name must not be null"); String storeAttributeName = this.getAttributeNameInSession(request, attributeName); return request.getAttribute(storeAttributeName, 1); } public void cleanupAttribute(WebRequest request, String attributeName) { Assert.notNull(request, "WebRequest must not be null"); Assert.notNull(attributeName, "Attribute name must not be null"); String storeAttributeName = this.getAttributeNameInSession(request, attributeName); request.removeAttribute(storeAttributeName, 1); } protected String getAttributeNameInSession(WebRequest request, String attributeName) { return this.attributeNamePrefix + attributeName; }}Copy the code

As you can see from the code, the SessionAttributeStore attribute is not a container for storing the SessionAttribute parameter, but a tool for storing the SessionAttribute, which is ultimately stored in the request


Set the expiration time of the cache

if (! Response.containsheader (HEADER_CACHE_CONTROL)) {// If contains SessionAttributes with name or type attributes if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { // Sets the cache time, cacheSecondsForSessionAttributeHandlers default 0 applyCacheSeconds (the response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); }} // cacheSeconds>0 set cache time,cacheSeconds=0 prevent cache protected final void applyCacheSeconds(HttpServletResponse Response, int cacheSeconds) { if (this.useExpiresHeader || ! Enclosing useCacheControlHeader) {/ / Deprecated HTTP 1.0 cache behaviors, as in previous Spring versions if (cacheSeconds > 0) { cacheForSeconds(response, cacheSeconds); } else if (cacheSeconds == 0) { preventCaching(response); } } else { CacheControl cControl; if (cacheSeconds > 0) { cControl = CacheControl.maxAge(cacheSeconds, TimeUnit.SECONDS); if (this.alwaysMustRevalidate) { cControl = cControl.mustRevalidate(); } } else if (cacheSeconds == 0) { cControl = (this.useCacheControlNoStore ? CacheControl.noStore() : CacheControl.noCache()); } else { cControl = CacheControl.empty(); } applyCacheControl(response, cControl); }}Copy the code

invokeHandlerMethod

InvokeHandlerMethod is a core method that performs the processing of the requestCopy the code

InvokeHandlerMethod source code:

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Throws Exception {// Use request and Response to create a ServletWebRequest ServletWebRequest webRequest = new ServletWebRequest(request, response); Try {// Initialize three variables WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); MavContainer = new ModelAndViewContainer(); // Create ModelAndViewContainer to hold Model and View ModelAndViewContainer. mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); // Asynchronous processing related... AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); if (logger.isDebugEnabled()) { logger.debug("Found concurrent result value [" + result + "]"); } invocableMethod = invocableMethod.wrapConcurrentResult(result); } / / processing request invocableMethod. InvokeAndHandle (webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); }}Copy the code
First create a webRequest of type ServletWebRequest ArgumentResolve using request and Response Then, three variables are initialized WebDataBinderFactory, ModelFactory, ServletInvocableHandlerMethod And then we create a New ModelAndViewContainer that passes the parameters, and we execute the request, and the request executes the post-processing that's doneCopy the code

WebDataBinderFactory initialization

WebDataBinderFactory:

Used to create the WebDataBinderCopy the code

WebDataBinder:

ArgumentResolve WebDataBinder ModelFactory is used for parameter resolution and is used for updating modelsCopy the code

GetDataBinderFactory source code:

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
    // Check whether the InitBinder method in the current Handler is already in the cacheClass<? > handlerType = handlerMethod.getBeanType(); Set<Method> methods =this.initBinderCache.get(handlerType);
    // If it is not in cache, find it and set it to cache
    if (methods == null) {
        methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
        this.initBinderCache.put(handlerType, methods);
    }
    // Save a collection of initBinder methods
    List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
    // Add all qualified global InitBinder methods to initBinderMethods
    for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
        if (entry.getKey().isApplicableToBeanType(handlerType)) {
            Object bean = entry.getKey().resolveBean();
            for(Method method : entry.getValue()) { initBinderMethods.add(createInitBinderMethod(bean, method)); }}}// Add the InitBinder method from the current Handler to initBinderMethods
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        initBinderMethods.add(createInitBinderMethod(bean, method));
    }
    // Create DataBinderFactory and return DataBinderFactory associated with the InitBinder method
    return createDataBinderFactory(initBinderMethods);
}

protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
        throws Exception {

    return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}
Copy the code
The WebDataBinderFactory creation process: To find qualified @ InitBinder method, use them to create ServletRequestDataBinderFactory types of WebDataBinderFactory InitBinder method consists of two parts: 1, comments the @ ControllerAdvice InitBinder method in the global processor and to meet the requirements of (create RequestMappingHandlerAdapter is set to the cache) 2. InitBinder methods that handle themselves (saved to cache after the first call) are added in the order global before selfCopy the code

ModelFactory initialization

ModelFactory:

The ModelFactory is used to process the Model. It has two functions: 1, it initializes the Model before processing, and 2, it updates Model parameters after processing the requestCopy the code

Model initialization consists of three parts:

1, set the value from the original SessionAttributes to Model. 2, execute the @modelAttribute method and set its value to Model 3. If the @modelAttribute parameter in the handler is also configured in SessionAttributes and does not have a value in mavContainer, look up the attributes in all SessionAttributes and set them (possibly values set by other handlers).Copy the code

The Model update:

SessionAttributes is set first. If the handler calls SessionStatus#setComplete, the SessionAttributes are cleared Otherwise, set the response parameter in defaultModel in mavContainer to SessionAttributes and then set the corresponding BindingResult for the parameter to Model as requiredCopy the code

GetModelFactory source code:

private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
    / / get SessionAttributesHandler
    SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
    // Get @modelAttribute without @requestMapping, add it to the cacheClass<? > handlerType = handlerMethod.getBeanType(); Set<Method> methods =this.modelAttributeCache.get(handlerType);
    if (methods == null) {
        methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
        this.modelAttributeCache.put(handlerType, methods);
    }
    List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
    // Add the global @controllerAdvice method first, followed by the current processor-defined @modelAttribute method
    for (Entry<ControllerAdviceBean, Set<Method>> entry : this.modelAttributeAdviceCache.entrySet()) {
        if (entry.getKey().isApplicableToBeanType(handlerType)) {
            Object bean = entry.getKey().resolveBean();
            for(Method method : entry.getValue()) { attrMethods.add(createModelAttributeMethod(binderFactory, bean, method)); }}}for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
    }

    / / create ModelFactory
    return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}
Copy the code
new ModelFactory(attrMethods, binderFactory, sessionAttrHandler); The @modelAttribute method is annotated with 3 arguments 1) the @modelAttribute method defined in the @ControllerAdvice class 2) the processor's own @modelAttribute method adds the global first and then its own 2, WebDataBinderFactory, front created 3, SessionAttributesHandler, obtained by getSessionAttributesHandlerCopy the code

ServletInvocableHandlerMethod initialization

ServletInvocableHandlerMethod inherited from HandlerMethod, and can be directly executed Processing methods, this method is the feeling be nasty request parameter binding, request processing, the return value processing is done in this methodCopy the code

Partial source code:

ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
    return new ServletInvocableHandlerMethod(handlerMethod);
}
Copy the code
The first to use handlerMethod has built a ServletInvocableHandlerMethod class And then set argumentResolvers returnValueHandlers, binderFactory parameterNameDiscovererCopy the code

ModelAndViewContainer initialization

ModelAndViewContainer is used to hold models and ViewsCopy the code

Partial source code:

// Create ModelAndViewContainer to hold Model and View ModelAndViewContainer mavContainer = new ModelAndViewContainer(); / / set to the data from the FlashMap Model mavContainer. AddAllAttributes (RequestContextUtils. GetInputFlashMap (request)); // Use modelFactory to set SessionAttributes and @modelAttributede method parameters to Model ModelFactory. initModel(webRequest, mavContainer, invocableMethod); / / ignoreDefaultModelOnRedirect according to the configuration Settings mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);Copy the code
The ModelAndViewContainer runs through the process, setting up three parts of the mavContainer: 1, set the FlashMap data to Model 2, use modelFactory to set SessionAttributes and the parameters of the annotated @ModelAttributede method to Model 3, according to the configuration of ignoreDefaultModelOnRedirect SettingsCopy the code
public void initModel(NativeWebRequest request, ModelAndViewContainer container,
        HandlerMethod handlerMethod) throws Exception {

    Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
    container.mergeAttributes(sessionAttributes);
    invokeModelAttributeMethods(request, container);

    for (String name : findSessionAttributeArguments(handlerMethod)) {
        if (!container.containsAttribute(name)) {
            Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
            if (value == null) {
                throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
            }
            container.addAttribute(name, value);
        }
    }
}
Copy the code

InvokeAndHandler performs the request

invocableMethod.invokeAndHandle(webRequest, mavContainer);
Copy the code

Direct call the ServletInvocableHandlerMethod# invokeAndHandle method performs the request, the subsequent this chapter, a detailed analysis


GetModelAndView Post-processing after request processing is complete

Related source code:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, Throws Exception {// call updateModel to updateModel (set SessionAttributes and set BindingResult to Model) modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; } // Create ModelAndView ModelMap Model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus()); if (! mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } // If model is RedirectAttributes type, set the value to FlashMap if (Model instanceof RedirectAttributes) {Map<String,? > flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; }Copy the code

Three things have been done:

Call updateModel to update the Model(with SessionAttributes set and BindingResult set for the Model) and create the ModelAndView based on the mavContainer If the Model is of RedirectAttributes type, set the value to the FlashMap. The Model will only be of RedirectAttributes type if the processor returns a redirect view. Even if variables are set using directAttributes, they are not saved to the FlashMap, which will be parsed in ModelAndViewContainer.Copy the code

Here the entire RequestMappingHandlerAdapter request handling process is finished There are many we don't have any analysis to the internal components, then we are analyzed one by oneCopy the code