SpringMVC Source Learning: Container Initialization + MVC Initialization + Request Distribution Processing + Parameter Resolution + Return Value Resolution + View Resolution

SpringMVC Source Learning: Container Initialization + MVC Initialization + Request Distribution Processing + Parameter Resolution + Return Value Resolution + View Resolution

Catalog
1. Preface
2. Initialization

  1. Container Initialization
    Ways to find the root container

Container Creation Method
Load profile information

  1. Initialization of MVC
    File Upload Parser

Zone Information Parser
handler Mapping Information Resolution

  1. Implementation principle of HandlerMapping
    HandlerExecutionChain

RequestMappingHandlerMapping
3. Processing of Request Response

  1. Request Distribution
  2. Request Processing
    Parameter parsing process

Pass Page Parameters
Return Value Resolution

  1. View Resolution
    view resolver

view
1. Preface
Edition:

springMVC 5.0.2RELEASE

JDK1.8

Configuration of Front End Controller:

web.xml

copy

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <!--Load the configuration file under the class path-->
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!--Create objects at server startup. The smaller the value, the higher the priority, and the first to create objects-->
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <!--Note that not /*, but /* intercepts requests such as *.jsp-->
    <url-pattern>/</url-pattern>
</servlet-mapping>

springmvc.xml Configuration

Copy <?Xml version="1.0" encoding="UTF-8"?>

   xmlns:mvc="http://www.springframework.org/schema/mvc"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Turn on Annotation Scan -->
<context:component-scan base-package="com.smday"/>

<!-- View Parser Object -->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
</bean>
<!-- open SpringMVC Support for framework notes -->
<mvc:annotation-driven/>
<!--Release static resources-->
<mvc:default-servlet-handler/>


2. Initialization

The startup of Dispatcher Servlet is closely related to the startup process of Servlet, which we can see from the inheritance diagram above.

  1. Container Initialization
    The init() method defined in the Servlet is the initialization method of its life cycle. Next, the GenericServlet does not give a specific implementation. The init() method in HttpServletBean gives a specific implementation:

HttpServletBean.init() method (ignoring logs)

Copy @Override

public final void init() throws ServletException {
    //Set the bean property based on the initialization parameters (we set the contextConfigLocation so we can get it)
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            //Packaging Dispatcher Servlet
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            //Get the resource loader to load the springMVC configuration file
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            //Register a ResourceEditor
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            //This method is empty and can be overridden to initialize BeanWrapper
            initBeanWrapper(bw);
            //Finally, the value spirng-mvc.xml read by init-param is stored in the contextConfigLocation
            bw.setPropertyValues(pvs, true);
        }
    }

    // Initialize subclasses
    initServletBean();

}

Let's see what FrameworfServlet.initServletBean() does (basically logging, timing, omitting these parts):

Copy/**

 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
 * have been set. Creates this servlet's WebApplicationContext.
 */
@Override
protected final void initServletBean() throws ServletException {
    //Initialization of WebApplicationContext
    this.webApplicationContext = initWebApplicationContext();
    //Is also an empty implementation, allowing subclasses to customize
    initFrameworkServlet();
}

So the highlight is on the initWebApplicationContext method, so let's first see what happens after execution:

You can see that the nine components of springMVC are assigned, in addition to the webApplicationContext.

Let's take a look at the source code and see its internal implementation: FrameworkServlet.initWebApplicationContext()

Copy protected WebApplicationContext initWebApplicationContext() {

//Root Container Find
WebApplicationContext rootContext =
    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
    //Dispatcher Servlet was injected at build time and webApplicationContext already exists - > Direct use
    wac = this.webApplicationContext;
    if (wac instanceof ConfigurableWebApplicationContext) {
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
        if (!cwac.isActive()) {
            //If the context does not have refresh-->set the parent context, the application context id, and so on
            if (cwac.getParent() == null) {
                //Injected context instance without explicit parent - > set root application context as parent
                cwac.setParent(rootContext);
            }
            configureAndRefreshWebApplicationContext(cwac);
        }
    }
}
if (wac == null) {
    //No context instances were injected during construction -->Query from ServletContext
    wac = findWebApplicationContext();
}
if (wac == null) {
    // Create a local one without --> in the ServletContext
    wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
    //Trigger onfresh manually if context does not support refresh or if it was already refresh at initialization
    onRefresh(wac);
}
//Save the currently established context in the ServletContext using an attribute name related to the current Servlet name
if (this.publishContext) {
    // Publish context as servlet context property
    String attrName = getServletContextAttributeName();
    getServletContext().setAttribute(attrName, wac);
}
return wac;

}
Ways to find the root container
Copy WebApplicationContext rootContext =

WebApplicationContextUtils.getWebApplicationContext(getServletContext());

WebApplicationContextUtils.getWebApplicationContext

Copy//SpringMVC supports the easy coexistence of Spring containers with the Web, and Spring containers are considered root containers and are usually loaded by ContextLoaderListener.
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {

//String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

}

@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {

//Find values for keys based on ServletName.ROOT
Object attr = sc.getAttribute(attrName);
if (attr == null) {
    return null;
return (WebApplicationContext) attr;

}
If both the Spring container and the Web container exist, the configuration of Spring needs to be loaded using ContextLoaderListener, and it will be key ed as

WebApplicationContext.class.getName() + ".ROOT is stored in the ServletContext.

Container Creation Method
When you build without any Context instance injection and no WebApplicationContext is found in the ServletContext, a local Context is created, which allows you to explicitly pass in the parent container as a parameter.

Copy protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {

//Default: DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; contextClass can be specified in initialization parameters
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    throw new ApplicationContextException(
        "Fatal initialization error in servlet with name '" + getServletName() +
        "': custom WebApplicationContext class [" + contextClass.getName() +
        "] is not of type ConfigurableWebApplicationContext");
}
//Get Configurable WebApplicationContext object
ConfigurableWebApplicationContext wac =
    (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
    wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);

return wac;

}
We can see that during this process, the IoC container of the Web container is created, also known as the XmlWebApplicationContext, which establishes the entire spring application in the Web container.

configureAndRefreshWebApplicationContext(wac);

Copy protected void configureAndRefreshWebApplicationContext (Configurable WebApplicationContext wac) {

//Omit setting some values to the ConfigurableWebApplicationContext object...
//Each context refresh calls initPropertySources
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
    ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//Initialize the webApplication container, restart
wac.refresh();

}
Load profile information
In fact, refresh() is the key method, which you should be familiar with when you first learned about the initialization of spring containers, or is it a three-step process:

BeanDefinition's esource location, where we located classpath:springmvc.xml.

During the beanDefinition loading process, springMVC made some changes, such as defining a namespace resolution MvcNamespaceHandler for mvc.

Next comes the registration of the beanDefinition in IoC, where the beanName:beanDefinition is stored as a key-value pair in the beandefinitionMap.

  1. Initialization of MVC
    Initialization of MVC is performed in Dispatcher Servlet's initStratefies method. With the method name, we can conclude that nine components are initialized here, essentially getting objects from the IoC container:

Copy protected void initStrategies(ApplicationContext context) {

    //File Upload Parser
    initMultipartResolver(context);
    //Regional information parser, related to internationalization
    initLocaleResolver(context);
    //theme resolver
    initThemeResolver(context);
    //handler Mapping Information Resolution
    initHandlerMappings(context);
    //Adapter for handler
    initHandlerAdapters(context);
    //handler exception parser
    initHandlerExceptionResolvers(context);
    //View Name Converter
    initRequestToViewNameTranslator(context);
    //view resolver
    initViewResolvers(context);
    //Flash Map Manager
    initFlashMapManager(context);
}

File Upload Parser
Copy private void initMultipartResolver(ApplicationContext context) {

try {
    this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
}
catch (NoSuchBeanDefinitionException ex) {
    // By default, multipartResolver is not configured.
    this.multipartResolver = null;
}

}
The profile upload parser is also simple, simply registering the MultipartResolver in the container opens the file upload function.

Zone Information Parser
Copy private void initLocaleResolver(ApplicationContext context) {

try {
    this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);

}
catch (NoSuchBeanDefinitionException ex) {
    // Create objects with reflection using default policy
    this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
}

}
The Dispatcher Servlet.properties file in the peer directory of org.springframework.web.servlet.Dispatcher Servlet specifies the default policy for initializing several components.

handler Mapping Information Resolution
Handler Mappings exist to find the corresponding Controller controller Controller for HTTP requests.

Copy private void initHandlerMappings(ApplicationContext context) {

    this.handlerMappings = null;
    //Import HandlerMappings from all IoC containers, including their parent context
    if (this.detectAllHandlerMappings) {
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            //Attempt to get from container
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }
    //Make sure there is at least one handler Mapping
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }
}

The next few operations are almost the same, so let's not go into details.

In general, the MVC initialization process is based on the IoC container initialization, after all, the component objects are removed from the container.

  1. Implementation principle of HandlerMapping
    HandlerExecutionChain

Handler Mapping plays a very important role in Spring MVC and we say it can find a corresponding Controller controller for HTTP requests, so let's take a closer look at what's hidden there.

HandlerMapping is an interface that contains a getHandler method that allows you to obtain a handlerExecutionChain corresponding to an HTTP request, which holds handler and interceptor lists in the handlerExecutionChain object, and methods related to setting interceptors.You can tell that these configured interceptors provide a wave of enhancements to the functionality provided by handler objects.

RequestMappingHandlerMapping
Let's take one of these HandlerMapping s as an example and focus on:

Copy protected void initHandlerMethods() {

//Get beanName in all contexts
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                      BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
                      obtainApplicationContext().getBeanNamesForType(Object.class));

for (String beanName : beanNames) {
    if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
        Class<?> beanType = null;
        //Get lass for corresponding beanName
        beanType = obtainApplicationContext().getType(beanName);
        //Determine whether it is a controller class
        if (beanType != null && isHandler(beanType)) {
            //Processing methods in the controller
            detectHandlerMethods(beanName);
        }
    }
}
handlerMethodsInitialized(getHandlerMethods());

}
isHandler method: Determines if the class has @Controller or @RequestMapping annotations

Copy @Override

protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

detectHandlerMethods method:

Copy protected void detectHandler Methods (final Object handler) {

//Get the type of controller
Class<?> handlerType = (handler instanceof String ?
                        obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
    //Processing the type again, mainly for cglib
    final Class<?> userType = ClassUtils.getUserClass(handlerType);
    //Traverse the method, process the information in the annotation, get the RequestMappingInfo object, get the methods array
    Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                                                              (MethodIntrospector.MetadataLookup<T>) method -> {
  return getMappingForMethod(method, userType);
     });
    //Traverse methods[Method,{path}]
    for (Map.Entry<Method, T> entry : methods.entrySet()) {
        //Check method accessibility, such as private, static, SpringProxy
        Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
        //Get the final request path
        T mapping = entry.getValue();
        //register
        registerHandlerMethod(handler, invocableMethod, mapping);
    }
}

}
Properties of the mapping object:

Elements stored in the methods object:

The registration method is implemented in AbstractHandlerMethodMapping:

Copy public void register(T mapping, Object handler, Method method) {

this.readWriteLock.writeLock().lock();
try {
    //Object of processing method
    HandlerMethod handlerMethod = createHandlerMethod(handler, method);
    //Determining the Uniqueness of Mapping
    assertUniqueMethodMapping(handlerMethod, mapping);
    //Correspond mapping information and controller methods
    this.mappingLookup.put(mapping, handlerMethod);
    //Map path to processor (one method may handle multiple url s)
    List<String> directUrls = getDirectUrls(mapping);
    for (String url : directUrls) {
        this.urlLookup.add(url, mapping);
    }
    //Capitalized English abbreviation for controller name #method name
    String name = null;
    if (getNamingStrategy() != null) {
        name = getNamingStrategy().getName(handlerMethod, mapping);
        addMappingName(name, handlerMethod);
    }
    //Cross-domain Request Related Configuration
    CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
    if (corsConfig != null) {
        this.corsLookup.put(handlerMethod, corsConfig);
    }
    //Register all configurations in the registry uniformly
    this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
    this.readWriteLock.writeLock().unlock();
}

}
At this point, all Controller s and the methods labeled with the @RequestMapping annotation are parsed and registered in HashMap, so that the corresponding request path matches the processing method, and HandlerMapping is initialized.

3. Processing of Request Response

  1. Request Distribution
    One thing we need to make clear is that when a request comes, where is the first execution? The Servlet service method. We just need to look at one of its subclasses:

service method overridden by FrameworkServlet:

Copy @Override

protected void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    //Get Request Method
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    //Intercept PATCH requests
    if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
        processRequest(request, response);
    }
    else {
        super.service(request, response);
    }
}

In fact, the final call is to the processRequest method, which also calls the real doService() method, in which the details are not discussed. Let's go straight ahead and see what Dispatcher Servlet's doService does (Dispatcher Servlet class is really the core of the core, building both the IoC container and responsible for request distribution):

Copy @Override

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//Ignore a large list of preliminary preparations to enable it to handle view objects
//Next to real distribution
doDispatch(request, response);
}

doService:

Copy 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 {
        //Wrap the request if it is a file upload request, or return it as it is
        processedRequest = checkMultipart(request);
        //File Upload Request Identifier
        multipartRequestParsed = (processedRequest != request);

        //Find the appropriate handler for the current request request
        mappedHandler = getHandler(processedRequest);
        //If no handler can handle the request, jump to the error page
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }
        //Find the appropriate adapter for the current request request
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        String method = request.getMethod();
        boolean isGet = "GET".equals(method);
        if (isGet || "HEAD".equals(method)) {
            //Determines whether getLastModified is supported or returns -1 if not
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                return;
            }
        }
        //Execute the preHandle method for registering interceptors
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }
        // The way requests are actually handled
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        if (asyncManager.isConcurrentHandlingStarted()) {
            return;
        }
        //If the mv!=null &&mv object does not have a View, set a default ViewName for the MV object
        applyDefaultViewName(processedRequest, mv);
        //Execute the applyPostHandle method for registering interceptors
        mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    //View parsing and rendering
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

}
It is important to note that mappedHandler and HandlerAdapter are lookups that are traversed from the corresponding set, and that once they find an executable target, they stop looking, and we can artificially define priorities and determine the order between them.

  1. Request Processing
    The handleInternal method of the RequestMappingHandlerAdapter, which contains the logic to actually process the request.

Copy @Override
protected ModelAndView handleInternal(HttpServletRequest request,

                                  HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//Define return value variable
ModelAndView mav;
//Check supportedMethods and requireSession for requests
checkRequest(request);

// See if synchronizeOnSession is on, defaulting to false
if (this.synchronizeOnSession) {
    HttpSession session = request.getSession(false);
    //Httpsession available
    if (session != null) {
        Object mutex = WebUtils.getSessionMutex(session);
        //Lock, all requests serialized
        synchronized (mutex) {
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    }
    else {
        // No Httpsession available ->No locks necessary
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }
}
else {
    // Normal Call Handling Method
    mav = invokeHandlerMethod(request, response, handlerMethod);
}
//Check if the response header contains Cache-Control
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
        applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
    }
    else {
        prepareResponse(response);
    }
}

return mav;

}
The invokeHandlerMethod method of the RequestMappingHandlerAdapter really returns mv.

Copy @Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,

                                       HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//Wrap HttpServletRequest to produce a request object for ServletWebRequest to process the web
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
    //Create a factory for the WebDataBinder object
    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    //Create Factory for Model Object
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    //Wrap handlerMethod objects to create ServletInvocableHandlerMethod objects
    //Set related properties to invocableMethod (invokeAndHandle method is last called by invocableMethod object)
    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    if (this.argumentResolvers != null) {
        invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
        invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }
    invocableMethod.setDataBinderFactory(binderFactory);
    invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    //Create a ModelAndViewContainer object that holds a map with data stored in the domain
    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    modelFactory.initModel(webRequest, mavContainer, invocableMethod);
    mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

    AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    asyncWebRequest.setTimeout(this.asyncRequestTimeout);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    //Omit Asynchronous Processing
    //Normal call
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    if (asyncManager.isConcurrentHandlingStarted()) {
        return null;
    }
    //Get ModelAndView object
    return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
    webRequest.requestCompleted();
}

}
invokeAndHandle method of ServletInvocableHandlerMethod: Reflect the call method to get the return value.

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

//Get parameters and return values by reflection
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//Set Response Status
setResponseStatus(webRequest);

if (returnValue == null) {
    if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
        mavContainer.setRequestHandled(true);
        return;
    }
}
else if (StringUtils.hasText(getResponseStatusReason())) {
    mavContainer.setRequestHandled(true);
    return;
}

mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
    //Processing return values
    this.returnValueHandlers.handleReturnValue(
        returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
    if (logger.isTraceEnabled()) {
        logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
    }
    throw ex;
}

}
Parameter parsing process
What we can know is that when you pass parameters, you can pass parameters like Map, Basic Type, POJO, ModelMap, and so on. What happens after parsing?It is easier to analyze with a specific example:

Copy @RequestMapping ('/handle03/{id}')

public String handle03(@PathVariable("id") String sid,
                       Map<String,Object> map){
    System.out.println(sid);
    map.put("msg","Hello!");
    return "success";
}

Copy/**

  • Gets the method parameter value of the current request.
    */

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

//Get Parameter Object
MethodParameter[] parameters = getMethodParameters();
//Create an array of equal size to store parameter values
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
    MethodParameter parameter = parameters[i];
    parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
    args[i] = resolveProvidedArgument(parameter, providedArgs);
    if (args[i] != null) {
        continue;
    }
    if (this.argumentResolvers.supportsParameter(parameter)) {
        //Parameter processor processing parameters (strategies with different types of processing parameters for different types of parameters)
        args[i] = this.argumentResolvers.resolveArgument(
            parameter, mavContainer, request, this.dataBinderFactory);
        continue;
    }
    if (args[i] == null) {
        throw new IllegalStateException();
    }
return args;

}
resolveArgument method:

Copy @Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

//Get annotation information
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
//Wrapping parameter objects
MethodParameter nestedParameter = parameter.nestedIfOptional();
//Gets the property name specified by @PathVariable
Object resolvedName = resolveStringValue(namedValueInfo.name);
//
if (resolvedName == null) {
    throw new IllegalArgumentException(
        "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
//Find and get parameter values from url based on name
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
//No match
if (arg == null) {
    //If there is a default value, look for it
    if (namedValueInfo.defaultValue != null) {
        arg = resolveStringValue(namedValueInfo.defaultValue);
    }
    //If required is false, name may not be specified, but defaults to true.
    else if (namedValueInfo.required && !nestedParameter.isOptional()) {
        handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
    }
    
    arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
//Although matched, the parameter passed in in the path is handled by default if it is "" and has a default name
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
    arg = resolveStringValue(namedValueInfo.defaultValue);
}

if (binderFactory != null) {
    WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
        arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);   
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;

}
getNameValueInfo method:

Copy private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {

    //Get from Cache
    NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
    if (namedValueInfo == null) {
        //Create a namedValueInfo object
        namedValueInfo = createNamedValueInfo(parameter);
        //If the property name is not specified in the comment, the default is the parameter name
        namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
        //Update Cache
        this.namedValueInfoCache.put(parameter, namedValueInfo);
    }
    return namedValueInfo;
}

createNamedValueInfo: Gets the information for the @PathVariable annotation and encapsulates it as a NamedValueInfo object

Copy @Override

protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
    PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
    Assert.state(ann != null, "No PathVariable annotation");
    return new PathVariableNamedValueInfo(ann);
}

updateNamedValueInfo:

Copy private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info){

String name = info.name;
if (info.name.isEmpty()) {
    //Parameter name if no name is specified in the comment
    name = parameter.getParameterName();
    if (name == null) {
        throw new IllegalArgumentException(
            "Name for argument type [" + parameter.getNestedParameterType().getName() +
            "] not available, and parameter name information not found in class file either.");
    }
}
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
return new NamedValueInfo(name, info.required, defaultValue);

}
resolveName method:

The process of parameter parsing:

Based on the method object, get an array of parameter objects and create an array to store the parameters.
Traverses through the parameter object array and parses it according to the parameter parser argumentResolver.
Error if there is no parameter parser.
When parsing a parameter, try to get information about the comment first, using @PathVariable as an example.
Gets the parameter value from the url based on the specified name, or defaults to the parameter name you passed in if it is not specified.
Pass Page Parameters
We may store key-value pairs into the domain through Map, Model, ModelMap, and so on, which are part of the request processing.





What we're interested in is the class ModelAndViewContainer, which contains BindingAwareModelMap by default.

A BindingAwareModelMap has already been initialized with the MapMethodProcessor parameter processor when parsing parameters.

Of course, the focus here is still on parameter parsing. As to why the data is encapsulated in the map, it's very simple. It's all about reflecting the execution method and put ting the data in, of course, the final data also exists in the ModelAndViewContainer.

Return Value Resolution
Omit the procedure to find the return value parser, because the return value is the view name, so the parser is ViewNameMethodReturnValueHandler.

Copy @Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

if (returnValue instanceof CharSequence) {
    //Get View Name
    String viewName = returnValue.toString();
    //Set to mavContainer
    mavContainer.setViewName(viewName);
    //Is it isRedirectViewName
    if (isRedirectViewName(viewName)) {
        mavContainer.setRedirectModelScenario(true);
    }
}
else if (returnValue != null){
    // should not happen
    throw new UnsupportedOperationException("Unexpected return type: " +
                                            returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}

}
isRedirectViewName method

Copy protected Boolean isRedirectViewName {

    //Does it conform to the custom redirectPatterns or the name beginning with redirect:
    return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
}

Finally, get the mv object through getModelAndView, let's take a closer look:

Copy @Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

//Promote model attributes listed as @SessionAttributes to the session
modelFactory.updateModel(webRequest, mavContainer);
//If the request has been processed
if (mavContainer.isRequestHandled()) {
    return null;
}
//Get our saved data map from mavContainer
ModelMap model = mavContainer.getModel();
//Create a ModelAndView object from view name, modelmap, and status
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
    mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
    Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
    if (request != null) {
        RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
    }
}
return mav;

}
The last thing returned is the ModelAndView object, which contains the logical name and the view of the model object.

Return value parsing is relatively simple:

Gets the corresponding return value parser based on the returned parameters.
Get the view name, mavContainer.setRedirectModelScenario(true) if redirect is required;
In other cases, simply set the ViewName view name property in the mvcContainer.
Finally, take out the model, status, viewName of mvcContainer and create mv object to return.
[Summary]



Both parameter parsing and return value parsing contain a large number of resolution strategies. The process of finding an appropriate parser is to first traverse the initialized parser table, then determine if asynchronous processing is required, determine if the return value type can be handled, and if possible, use the parser to parse. If not, traverse down until the table is overrunUntil you have a parser.

  1. View Resolution
    Copy private void process DispatchResult (HttpServletRequest request, HttpServletResponse response, @Nullable Handler ExecutionChain mappedHandler, @Nullable ModelAndView mv,

@Nullable Exception exception) throws Exception {

boolean errorView = false;
// Guarantee rendering once, cleared as tag
if (mv != null && !mv.wasCleared()) {
    //Rendering process!!!
    render(mv, request, response);
    if (errorView) {
        WebUtils.clearErrorRequestAttributes(request);
    }
}

}
render method of Dispatcher Servlet

Copy protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {

// Determine locale for request and apply it to the response.
Locale locale =
    (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);

View view;
//Get View Name
String viewName = mv.getViewName();
if (viewName != null) {
    //Create a view object by processing the view name with the view resolver viewResolvers
    view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
}
else {
    view = mv.getView();
}
if (mv.getStatus() != null) {
    response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);   

}
Get the view parser and parse the view name:

Copy @Nullable
protected View resolveViewName(String viewName, @Nullable Map model,
Locale locale, HttpServletRequest request) throws Exception {

//Here we are registering InternalResourceViewResolver
if (this.viewResolvers != null) {
    for (ViewResolver viewResolver : this.viewResolvers) {
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
            return view;
        }
    }
}
return null;

}
createView method of UrlBasedViewResolver:

Copy @Override

protected View createView(String viewName, Locale locale) throws Exception {
    //If the parser cannot process the given view, return null and let the next parser see if it can execute
    if (!canHandle(viewName, locale)) {
        return null;
    }
    // Check for special "redirect:" prefix.
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
        //Determine if redirection is required
    }
    // Check for special "forward:" prefix.
    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
        //Determine whether forwarding is required
    }
    //Call the loadView method of the parent class
    return super.createView(viewName, locale);
}

Last returned view object:

View resolver viewResolver -- instantiation - > View (stateless, no thread security issues)

render method of AbstractView

Copy @Override
public void render(@Nullable Map model, HttpServletRequest request,
HttpServletResponse response) throws Exception {


//Get the merged map, the map we have stored in the domain, the key value for PathVariable, and so on
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//Render internal resources based on a given model, such as setting the model to the property of request
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

}
renderMergedOutputModel for InternalResourceView

Copy @Override
protected void renderMergedOutputModel(

Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

//Set the value in the model to the request field
exposeModelAsRequestAttributes(model, request);

// Set helpers for request, if any
exposeHelpers(request);

// Set target address in request
String dispatcherPath = prepareForRendering(request, response);

// Get RequestDispatcher for the target resource (usually JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);

// If it already contains or the response has been submitted, then include is performed, otherwise forward.
if (useInclude(request, response)) {
    response.setContentType(getContentType());
    rd.include(request, response);
}
else {
    // Note: The forwarded resource should determine the content type itself.
    rd.forward(request, response);
}

}
exposeModelAsRequestAttributes

Copy protected void exposeModelAsRequestAttributes(Map model,HttpServletRequest request) throws Exception {

//Traversing model
model.forEach((modelName, modelValue) -> {
    if (modelValue != null) {
        //Set value to request
        request.setAttribute(modelName, modelValue);
    }
    else {
        //Remove the name if value is null
        request.removeAttribute(modelName);
    }
});

}
view resolver
View resolver (implements the ViewResolver interface): parses a logical view into a specific view object.

Each view parser implements the Ordered interface and opens the order attribute, with lower order being more preferred.

Resolve the logical view name in the view resolver's order of priority until the resolution succeeds and the view object is returned, or an exception is thrown.

view
View (implements the View interface): Renders model data and presents it to the user in some form.

The final view object is used to render the model data, the processor does not care, the processor cares about the production model data, and decouples.

Author: Tencho Bacha
Source: https://www.cnblogs.com/summerday152/

Tags: Spring xml Session JSP

Posted on Sun, 10 May 2020 01:20:44 -0400 by peyups