SpringMVC Source Learning: Container Initialization + MVC Initialization + Request Distribution Processing + Parameter Resolution + Return Value Resolution + View Resolution
Catalog
1. Preface
2. Initialization
- Container Initialization
Ways to find the root container
Container Creation Method
Load profile information
- Initialization of MVC
File Upload Parser
Zone Information Parser
handler Mapping Information Resolution
- Implementation principle of HandlerMapping
HandlerExecutionChain
RequestMappingHandlerMapping
3. Processing of Request Response
- Request Distribution
- Request Processing
Parameter parsing process
Pass Page Parameters
Return Value Resolution
- 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"> <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.
- 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.
- 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.
- 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,] 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
- 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.
- 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/')
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.
- 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/