SpringMVC 执行流程解析

SpringMVC 执行流程解析

注:SpringMVC 版本 5.2.15

在这里插入图片描述
上面这张图许多人都看过,本文试图从源码的角度带大家分析一下该过程。

1. ContextLoaderListener

首先我们从 ContextLoaderListener 讲起,它继承自 ServletContextListener,用于监听 Web 应用程序的启动与停止。ContextLoaderListener 中的 contextInitialized() 方法用于初始化 Web 应用程序上下文。

ContextLoaderListener # contextInitialized

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {.../*** Initialize the root web application context.*/@Overridepublic void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());}...
}

其中又调用了 initWebApplicationContext() 方法初始化 Web 应用程序上下文

ContextLoader # initWebApplicationContext

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +"check whether you have multiple ContextLoader* definitions in your web.xml!");}servletContext.log("Initializing Spring root WebApplicationContext");Log logger = LogFactory.getLog(ContextLoader.class);if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {if (this.context == null) {// 创建并保存应用程序上下文到属性中this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {if (cwac.getParent() == null) {ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}// 配置并刷新当前 Web 应用程序上下文configureAndRefreshWebApplicationContext(cwac, servletContext);}}servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;}else if (ccl != null) {currentContextPerThread.put(ccl, this.context);}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");}return this.context;}catch (RuntimeException | Error ex) {logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}
}

该方法中调用了 createWebApplicationContext() 方法创建了 Web 应用程序上下文,并调用 configureAndRefreshWebApplicationContext() 方法配置并刷新当前 Web 应用程序上下文。

ContextLoader # configureAndRefreshWebApplicationContext

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {// 为当前 Web 应用程序上下文设置一个 idif (ObjectUtils.identityToString(wac).equals(wac.getId())) {String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);if (idParam != null) {wac.setId(idParam);}else {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(sc.getContextPath()));}}// 设置 ServletContextwac.setServletContext(sc);String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);}ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}customizeContext(sc, wac);// 刷新当前应用程序上下文wac.refresh();
}

该方法的主要作用是刷新当前应用程序上下文,实际上就是调用了 AbstractApplicationContext # refresh 方法,也就是我们常讲的启动 IOC 容器,这个方法里的具体内容这里就不讲了。

总结:
ContextLoaderListener 的作用是准备好 Web 应用程序上下文,启动 IOC 容器。

2. DispatcherServlet 初始化逻辑

先看一张 DispatcherServlet 的继承关系图
在这里插入图片描述
其中 HttpServletBean 有一个 init() 方法,该方法是一个初始化方法

HttpServletBean # init

@Override
public final void init() throws ServletException {// 获取 web.xml 文件中的 <init-param> 中的参数保存到 PropertyValues 中PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {// 将 HttpServletBean 对象转换为 BeanWrapper 对象BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);// 创建一个 ResourceLoader 对象ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());// 注册属性编辑器bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));// 初始化 BeanWrapper 对象initBeanWrapper(bw);// 将属性值保存到 BeanWrapper 中bw.setPropertyValues(pvs, true);}catch (BeansException ex) {if (logger.isErrorEnabled()) {logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);}throw ex;}}// Let subclasses do whatever initialization they like.initServletBean();
}

该方法中首先尝试获取 web.xml 文件中的 init-param 值,如果获取到的话,则将它保存到 BeanWrapper 中。最后调用了 initServletBean() 方法

FrameworkServlet # initServletBean

@Override
protected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");if (logger.isInfoEnabled()) {logger.info("Initializing Servlet '" + getServletName() + "'");}long startTime = System.currentTimeMillis();try {// 初始化 WebApplicationContextthis.webApplicationContext = initWebApplicationContext();// 空方法,可由子类去具体实现initFrameworkServlet();}catch (ServletException | RuntimeException ex) {logger.error("Context initialization failed", ex);throw ex;}if (logger.isDebugEnabled()) {String value = this.enableLoggingRequestDetails ?"shown which may lead to unsafe logging of potentially sensitive data" :"masked to prevent unsafe logging of potentially sensitive data";logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +"': request parameters and headers will be " + value);}if (logger.isInfoEnabled()) {logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");}
}

该方法中主要调用了 initWebApplicationContext() 去初始化 web 应用程序上下文

FrameworkServlet # initWebApplicationContext

protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext);}// 配置并刷新 web 应用程序上下文configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {// 去获取 web 应用程序上下文wac = findWebApplicationContext();}if (wac == null) {// 创建 web 应用程序上下文wac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {synchronized (this.onRefreshMonitor) {// 刷新 web 应用程序上下文onRefresh(wac);}}if (this.publishContext) {String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;
}

该方法主要是去创建一个新的 web 应用程序上下文,然后调用 onRefresh() 方法

FrameworkServlet # onRefresh

@Override
protected void onRefresh(ApplicationContext context) {initStrategies(context);
}

DispatcherServlet # initStrategies

protected void initStrategies(ApplicationContext context) {// 初始化 文件上传解析器initMultipartResolver(context);// 初始化 本地化解析器initLocaleResolver(context);// 初始化 主题解析器initThemeResolver(context);// 初始化 处理器映射器initHandlerMappings(context);// 初始化 处理器映射适配器initHandlerAdapters(context);// 初始化 异常解析器initHandlerExceptionResolvers(context);// 初始化 请求获取视图名转换器initRequestToViewNameTranslator(context);// 初始化 视图解析器initViewResolvers(context);// 初始化 FlashMap 管理器initFlashMapManager(context);
}

该方法中初始化了 SpringMVC 的九大组件。我以 initHandlerMappings() 方法为例讲解一下,其他组件的获取方式和它基本相同。

DispatcherServlet # initHandlerMappings

/*** Initialize the HandlerMappings used by this class.* <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,* we default to BeanNameUrlHandlerMapping.*/
private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;// 从 ApplicationContext 中去获取 HandlerMapping// 获取不到的话去获取对象名为 handlerMapping 的 HandlerMapping 对象// 再获取不到的话则注册默认的 HandlerMappingif (this.detectAllHandlerMappings) {// 从 ApplicationContext 中获取Map<String, HandlerMapping> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());AnnotationAwareOrderComparator.sort(this.handlerMappings);}}else {try {// 获取名为 handlerMapping 的 HandlerMapping 对象HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);}catch (NoSuchBeanDefinitionException ex) {}}if (this.handlerMappings == null) {// 注册默认的 HandlerMappingthis.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}
}

该方法首先从 ApplicationContext 中去获取 HandlerMapping,获取不到的话去获取对象名为 handlerMapping 的 HandlerMapping 对象,再获取不到的话则注册默认的 HandlerMapping。我们看一下这个默认的 HandlerMapping 是怎么获取的。

DispatcherServlet # getDefaultStrategies

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {String key = strategyInterface.getName();// 获取逻辑在这String value = defaultStrategies.getProperty(key);if (value != null) {// 获取类名String[] classNames = StringUtils.commaDelimitedListToStringArray(value);List<T> strategies = new ArrayList<>(classNames.length);for (String className : classNames) {try {// 创建 Class 对象Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());Object strategy = createDefaultStrategy(context, clazz);strategies.add((T) strategy);}catch (ClassNotFoundException ex) {throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class [" + className +"] for interface [" + key + "]", ex);}catch (LinkageError err) {throw new BeanInitializationException("Unresolvable class definition for DispatcherServlet's default strategy class [" +className + "] for interface [" + key + "]", err);}}return strategies;}else {return new LinkedList<>();}
}

可以看到会尝试从 defaultStrategies 中获取值。然后将获取到的值通过 Class.forName() 方法去创建对象。那么我们看一下这个 defaultStrategies 是什么。

private static final Properties defaultStrategies;static {try {ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);}catch (IOException ex) {throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());}
}
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

defaultStrategies 是 DispatcherServlet 中的一个属性,类型为 Properties 。在 DispatcherServlet 的静态代码块中加载了 DispatcherServlet.properties 文件到了 defaultStrategies 中。
在这里插入图片描述

DispatcherServlet.properties

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

在 DispatcherServlet.properties 中定义了 HandlerMapping、HandlerAdapter、ViewResolver 等类。它们的获取逻辑是相似的。通过文件中定义的全限定名,然后调用 Class.forName() 方法去创建对象。

3. DispatcherServlet 处理流程

以 get 请求为例。当发送 get 请求时,由 FrameworkServlet # doGet 方法进行处理。

FrameworkServlet # doGet

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}

该方法中又调用了 processRequest() 方法

FrameworkServlet # processRequest

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {long startTime = System.currentTimeMillis();Throwable failureCause = null;// 获取一个 LocaleContext 对象LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();// 构建一个新的 LocaleContext 对象LocaleContext localeContext = buildLocaleContext(request);// 获取一个 RequestAttributes 对象RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();// 构建一个新的 ServletRequestAttributes 对象ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());// 初始化资源持有者// 将 localeContext 保存到 LocaleContextHolder 中// 将 requestAttributes 保存到 RequestContextHolder 中initContextHolders(request, localeContext, requestAttributes);try {doService(request, response);}catch (ServletException | IOException ex) {failureCause = ex;throw ex;}catch (Throwable ex) {failureCause = ex;throw new NestedServletException("Request processing failed", ex);}finally {resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);publishRequestHandledEvent(request, response, startTime, failureCause);}
}

该方法中新建了一个 LocaleContext 对象和一个 ServletRequestAttributes 对象,并保存到了 LocaleContextHolder 与 RequestContextHolder 中。尤其是 RequestContextHolder 对象,我们可以通过它去获取 HttpServletRequest、HttpServletResponse 等对象。最后调用了 doService() 方法。

DispatcherServlet # doService

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);// 保留 request attributes 的快照Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap<>();Enumeration<?> attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// Make framework objects available to handlers and view objects.request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());if (this.flashMapManager != null) {FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);}try {doDispatch(request, response);}finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}
}

该方法中又调用了 doDispatch() 方法

DispatcherServlet # doDispatch

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 {// 检查是否是上传文件的请求processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 获取 HandlerExecutionChain // 该方法中确定了用哪个处理器处理当前请求// 并添加了拦截器mappedHandler = getHandler(processedRequest);// 没有找到合适的处理器if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 获取 HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 获取请求方法String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}// 执行拦截器的前置处理if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 处理请求并返回 ModelAndView 对象mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);// 执行拦截器的后置处理mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}// 处理最终的结果processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
}

接下来我们看一下 getHandler() 方法

DispatcherServlet # getHandler

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {// 遍历所有的 HandlerMapping 去获取 HandlerExecutionChain for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;
}

该方法中遍历了所有的 HandlerMapping 去获取 Handler

AbstractHandlerMapping # getHandler

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 获取合适的 HandlerObject handler = getHandlerInternal(request);if (handler == null) {// 没有找到合适的 Handler,则使用默认的 Handlerhandler = getDefaultHandler();}if (handler == null) {return null;}// 获取到了该 Handler 的字符串名,则通过字符串名去获取对应的对象if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}// 为当前请求添加拦截器HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);if (logger.isTraceEnabled()) {logger.trace("Mapped to " + handler);}else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {logger.debug("Mapped to " + executionChain.getHandler());}if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);config = (config != null ? config.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;
}

该方法中去获取合适的 Handler,获取失败的话,则会使用默认的 Handler。并为该请求添加拦截器。

RequestMappingInfoHandlerMapping # getHandlerInternal

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);try {return super.getHandlerInternal(request);}finally {ProducesRequestCondition.clearMediaTypesAttribute(request);}
}

AbstractHandlerMethodMapping # getHandlerInternal

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {// 获取请求路径String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);request.setAttribute(LOOKUP_PATH, lookupPath);// 加读锁this.mappingRegistry.acquireReadLock();try {// 根据请求路径获取对应的 HandlerMethod HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);}finally {// 释放读锁this.mappingRegistry.releaseReadLock();}
}

该方法中根据请求路径去获取对应的 HandlerMethod

AbstractHandlerMethodMapping # lookupHandlerMethod

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<>();// 根据请求路径获取对应的映射List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {// 获取匹配的映射// 这里面会做一系列的比较// 如:方法名比较、参数比较、请求头比较等addMatchingMappings(directPathMatches, matches, request);}if (matches.isEmpty()) {// No choice but to go through all mappings...addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}// 匹配到合适的方法,则从中找到最匹配的方法if (!matches.isEmpty()) {Match bestMatch = matches.get(0);if (matches.size() > 1) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));matches.sort(comparator);bestMatch = matches.get(0);if (logger.isTraceEnabled()) {logger.trace(matches.size() + " matching mappings: " + matches);}if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;}Match secondBestMatch = matches.get(1);if (comparator.compare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();Method m2 = secondBestMatch.handlerMethod.getMethod();String uri = request.getRequestURI();throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");}}request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);handleMatch(bestMatch.mapping, lookupPath, request);return bestMatch.handlerMethod;}// 没有找到合适的方法,返回 nullelse {return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}
}

该方法中会去获取与请求最匹配的方法,获取不到则返回 null。

这里就已经获取完 handler 了,也就是一个 HandlerMethod 对象,比如 TestController # test 。我们往下走。

@Controller
public class TestController {@GetMapping("/test")public String test() {return "test";}
}

DispatcherServlet # getHandlerAdapter

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}}}throw new ServletException("No adapter for handler [" + handler +"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

该方法就是去找一个对应的 HandlerAdapter,一般是 RequestMappingHandlerAdapter。

AbstractHandlerMethodAdapter # handle

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);
}

RequestMappingHandlerAdapter # handleInternal

protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;checkRequest(request);// Execute invokeHandlerMethod in synchronized block if required.if (this.synchronizeOnSession) {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 necessarymav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No synchronization on session demanded at all...mav = invokeHandlerMethod(request, response, handlerMethod);}if (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav;
}

该方法中调用了 invokeHandlerMethod() 方法

RequestMappingHandlerAdapter # invokeHandlerMethod

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 找到 @InitBinder 标注的方法// @InitBinder 与 WebDatabinder 一起使用,用于注册自定义的属性编辑器WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);// 找到 @ModelAttribute 标注的方法ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);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);ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));// 调用 @ModelAttribute 标注的方法,保证它在其他方法执行前先被执行modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);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();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}// 调用该方法invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}// 返回 ModelAndView 对象return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}
}

该方法中会找到并注册 @InitBinder 标注的方法,找到并调用 @ModelAttribute 标注的的方法。调用 invokeAndHandleI() 方法,最终返回 ModelAndView 对象

ServletInvocableHandlerMethod # invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 调用方法并获取返回值Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);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 {// 使用对应的 HandlerMethodReturnValueHandler 去处理返回值this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;}
}

InvocableHandlerMethod # invokeForRequest

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 获取形参值Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}// 调用方法return doInvoke(args);
}

InvocableHandlerMethod # getMethodArgumentValues

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 获取 MethodParameter 数组MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {// 解析参数args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg = ex.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;
}

该方法中调用了 resolveArgument() 去解析参数

HandlerMethodArgumentResolverComposite # resolveArgument

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 获取 HandlerMethodArgumentResolver -> RequestParamMethodArgumentResolverHandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);if (resolver == null) {throw new IllegalArgumentException("Unsupported parameter type [" +parameter.getParameterType().getName() + "]. supportsParameter should be called first.");}// 解析参数return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

AbstractNamedValueMethodArgumentResolver # resolveArgument

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");}Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg == null) {if (namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}catch (ConversionNotSupportedException ex) {throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}catch (TypeMismatchException ex) {throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;
}

AbstractNamedValueMethodArgumentResolver # getNamedValueInfo

private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {// 先从缓存中获取NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);// 缓存中获取不到if (namedValueInfo == null) {// 获取 @RequestParam 中的 value 属性值,如果有,则将参数名设置为该值namedValueInfo = createNamedValueInfo(parameter);// 如果没有使用 @RequestParam 注解或使用了但没有设置 value 属性// 则使用 ASM 框架去获取字节码来获取属性名namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);this.namedValueInfoCache.put(parameter, namedValueInfo);}return namedValueInfo;
}

RequestParamMethodArgumentResolver # createNamedValueInfo

protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {// 根据 @RequestParam 注解去创建 NamedValueInfo // 如果 @RequestParam 中的 value 属性有值,则设置参数名为该值RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}

AbstractNamedValueMethodArgumentResolver # updateNamedValueInfo

private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {String name = info.name;if (info.name.isEmpty()) {name = parameter.getParameterName();if (name == null) {throw new IllegalArgumentException("Name for argument of type [" + parameter.getNestedParameterType().getName() +"] not specified, 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);
}

MethodParameter # getParameterName

public String getParameterName() {if (this.parameterIndex < 0) {return null;}ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;if (discoverer != null) {String[] parameterNames = null;if (this.executable instanceof Method) {parameterNames = discoverer.getParameterNames((Method) this.executable);}else if (this.executable instanceof Constructor) {parameterNames = discoverer.getParameterNames((Constructor<?>) this.executable);}if (parameterNames != null) {this.parameterName = parameterNames[this.parameterIndex];}this.parameterNameDiscoverer = null;}return this.parameterName;
}

LocalVariableTableParameterNameDiscoverer # getParameterNames

public String[] getParameterNames(Method method) {Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);return doGetParameterNames(originalMethod);
}

LocalVariableTableParameterNameDiscoverer # doGetParameterNames

private String[] doGetParameterNames(Executable executable) {Class<?> declaringClass = executable.getDeclaringClass();Map<Executable, String[]> map = this.parameterNamesCache.computeIfAbsent(declaringClass, this::inspectClass);return (map != NO_DEBUG_INFO_MAP ? map.get(executable) : null);
}

LocalVariableTableParameterNameDiscoverer # inspectClass

private Map<Executable, String[]> inspectClass(Class<?> clazz) {// 获取字节码文件InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));if (is == null) {if (logger.isDebugEnabled()) {logger.debug("Cannot find '.class' file for class [" + clazz +"] - unable to determine constructor/method parameter names");}return NO_DEBUG_INFO_MAP;}try {ClassReader classReader = new ClassReader(is);Map<Executable, String[]> map = new ConcurrentHashMap<>(32);// ASM 框架提升字节码文件classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);return map;}catch (IOException ex) {if (logger.isDebugEnabled()) {logger.debug("Exception thrown while reading '.class' file for class [" + clazz +"] - unable to determine constructor/method parameter names", ex);}}catch (IllegalArgumentException ex) {if (logger.isDebugEnabled()) {logger.debug("ASM ClassReader failed to parse class file [" + clazz +"], probably due to a new Java class file version that isn't supported yet " +"- unable to determine constructor/method parameter names", ex);}}finally {try {is.close();}catch (IOException ex) {}}return NO_DEBUG_INFO_MAP;
}

走到这一步便是通过 ASM 框架来提升字节码文件获取参数名称了。所以推荐使用 @ReuquestParam 并设置 value 属性值,这样可以直接获取到参数名,避免了 ASM 框架编辑字节码所带来的性能消耗。

DispatcherServlet # processDispatchResult

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}if (mv != null && !mv.wasCleared()) {// 渲染 ModelAndViewrender(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isTraceEnabled()) {logger.trace("No view rendering, null ModelAndView returned.");}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}if (mappedHandler != null) {// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null);}
}

本方法中调用了 render() 方法去渲染视图

DispatcherServlet # render

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {Locale locale =(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());response.setLocale(locale);View view;String viewName = mv.getViewName();if (viewName != null) {// 根据 viewName 解析出 Viewview = resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() +"' in servlet with name '" + getServletName() + "'");}}else {// No need to lookup: the ModelAndView object contains the actual View object.view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");}}// Delegate to the View object for rendering.if (logger.isTraceEnabled()) {logger.trace("Rendering view [" + view + "] ");}try {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "]", ex);}throw ex;}
}

本方法中调用了 resolveViewName() 去获取 View

DispatcherServlet # resolveViewName

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,Locale locale, HttpServletRequest request) throws Exception {if (this.viewResolvers != null) {for (ViewResolver viewResolver : this.viewResolvers) {// 使用 ViewResolver 去解析出 ViewView view = viewResolver.resolveViewName(viewName, locale);if (view != null) {return view;}}}return null;
}

本方法中找到合适的 ViewResolver 去解析出 View

public View resolveViewName(String viewName, Locale locale) throws Exception {// 是否启用了缓存功能,默认启用了if (!isCache()) {return createView(viewName, locale);}else {// 先尝试从缓存中取 ViewObject cacheKey = getCacheKey(viewName, locale);View view = this.viewAccessCache.get(cacheKey);if (view == null) {synchronized (this.viewCreationCache) {view = this.viewCreationCache.get(cacheKey);if (view == null) {// 缓存中没有去到,交给子类去创建 Viewview = createView(viewName, locale);if (view == null && this.cacheUnresolved) {view = UNRESOLVED_VIEW;}if (view != null && this.cacheFilter.filter(view, viewName, locale)) {this.viewAccessCache.put(cacheKey, view);this.viewCreationCache.put(cacheKey, view);}}}}else {if (logger.isTraceEnabled()) {logger.trace(formatKey(cacheKey) + "served from cache");}}return (view != UNRESOLVED_VIEW ? view : null);}
}

本方法中判断是否开启了视图缓存功能,默认是开启了的。先尝试从缓存中取 View,取不到的话则交给子类去创建 View。

UrlBasedViewResolver # createView

@Override
protected View createView(String viewName, Locale locale) throws Exception {// 判断该视图解析器是否能处理这个视图// 如果不能的话,交给其他视图解析器去处理if (!canHandle(viewName, locale)) {return null;}// 检查前缀是否为 "redirect:"// 有的话则是请求重定向if (viewName.startsWith(REDIRECT_URL_PREFIX)) {// 截取 "redirect:" 后的字符串String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());// 创建重定视图RedirectView view = new RedirectView(redirectUrl,isRedirectContextRelative(), isRedirectHttp10Compatible());String[] hosts = getRedirectHosts();if (hosts != null) {view.setHosts(hosts);}return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);}// 检查前缀是否为 "forward:"// 有的话则是请求转发if (viewName.startsWith(FORWARD_URL_PREFIX)) {String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());InternalResourceView view = new InternalResourceView(forwardUrl);return applyLifecycleMethods(FORWARD_URL_PREFIX, view);}// 如果没有加这两个前缀的话,则回调给父类处理// 其实最终的处理结果是跟请求转发一样的return super.createView(viewName, locale);
}

该方法中会获取字符串前缀。前缀为 “redirect:” 则是请求重定向,为 “forward:” 则是请求转发。如果没有这两个前缀的话,又回调到父类 AbstractCachingViewResolver # createView 方法中。

AbstractCachingViewResolver # createView

protected View createView(String viewName, Locale locale) throws Exception {return loadView(viewName, locale);
}

回调到父类 AbstractCachingViewResolver # createView 方法,并调用了 loadView() 方法

InternalResourceViewResolver # buildView

protected AbstractUrlBasedView buildView(String viewName) throws Exception {// 可以看到,这里创建的 View 类型跟请求转发的相同InternalResourceView view = (InternalResourceView) super.buildView(viewName);if (this.alwaysInclude != null) {view.setAlwaysInclude(this.alwaysInclude);}view.setPreventDispatchLoop(true);return view;
}

本方法中调用了 buildView() 方法创建 View,可以看到,这里创建的 View 类型跟请求转发的相同

UrlBasedViewResolver # buildView

protected AbstractUrlBasedView buildView(String viewName) throws Exception {Class<?> viewClass = getViewClass();Assert.state(viewClass != null, "No view class");AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);// 拼接上前缀和后缀// 可以从 web.xml 文件中获取view.setUrl(getPrefix() + viewName + getSuffix());view.setAttributesMap(getAttributesMap());String contentType = getContentType();if (contentType != null) {view.setContentType(contentType);}String requestContextAttribute = getRequestContextAttribute();if (requestContextAttribute != null) {view.setRequestContextAttribute(requestContextAttribute);}Boolean exposePathVariables = getExposePathVariables();if (exposePathVariables != null) {view.setExposePathVariables(exposePathVariables);}Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();if (exposeContextBeansAsAttributes != null) {view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);}String[] exposedContextBeanNames = getExposedContextBeanNames();if (exposedContextBeanNames != null) {view.setExposedContextBeanNames(exposedContextBeanNames);}return view;
}

本方法中将 viewName 拼接了前缀和后缀,获取到的便是最终的完整路径。这里并将 View 获取成功了。

AbstractView # render

public void render(@Nullable Map<String, ?> model, HttpServletRequest request,HttpServletResponse response) throws Exception {if (logger.isDebugEnabled()) {logger.debug("View " + formatViewName() +", model " + (model != null ? model : Collections.emptyMap()) +(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));}// 创建并合并属性到 Map 中// 如:保存到 ModelAndView 或 Model 中的属性Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);// 准备好 HttpServletResponse 对象prepareResponse(request, response);// 解析出视图renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

该方法中主要是将我们自己保存到 ModelAndView 或 Model 中的属性转换为 Map 对象。最后调用 renderMergedOutputModel() 方法。

InternalResourceView # renderMergedOutputModel

protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// 将属性保存到 request 域中exposeModelAsRequestAttributes(model, request);// Expose helpers as request attributes, if any.exposeHelpers(request);// 确定请求的 urlString dispatcherPath = prepareForRendering(request, response);// 获取一个请求转发器RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);if (rd == null) {throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +"]: Check that the corresponding file exists within your web application archive!");}// 是一个 include 请求,jsp 中经常用到,实现 jsp 页面的复用if (useInclude(request, response)) {response.setContentType(getContentType());if (logger.isDebugEnabled()) {logger.debug("Including [" + getUrl() + "]");}rd.include(request, response);}else {if (logger.isDebugEnabled()) {logger.debug("Forwarding to [" + getUrl() + "]");}// 请求转发rd.forward(request, response);}
}

4. 总结

SpringMVC 处理请求的大致流程就是这样了。内容有点多,建议最好还是自己 debug 走一遍代码,梳理出整个脉络才好。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/556994.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无线网服务器mac是什么,电脑MAC和LAN MAC以及WIRELESS MAC是什么关系?

满意答案刘义芳aaa推荐于 2017.12.14采纳率&#xff1a;51% 等级&#xff1a;12已帮助&#xff1a;19753人一楼和二楼的回答都是对的电脑MAC这样说不好理解的&#xff0c;应该说MAC电脑&#xff0c;MAC是Macintosh这个的前三个字母&#xff0c;至于它为什么只用前三个字母做…

@RequestParam详解

RequestParam 主要用于将请求参数区域的数据映射到控制层方法的参数上 首先我们需要知道RequestParam注解主要有哪些参数 value&#xff1a;请求中传入参数的名称&#xff0c;如果不设置后台接口的value值&#xff0c;则会默认为该变量名。比如上图中第一个参数如果不设置va…

springboot 的 RedisTemplate 的 execute 和 executePipelined 功能的区别redis

1.executespring 如下是 springboot 官网原文:springboot Redis provides support for transactions through the multi, exec, and discard commands. These operations are available on RedisTemplate, however RedisTemplate is not guaranteed to execute all operatio…

Error running ‘Tomcat‘: Unable to open debugger port (127.0.0.1:2148): java.net.SocketExceptio

在Web项目运行的时候&#xff0c;IDEA可能会报Error running ‘Tomcat’: Unable to open debugger port (127.0.0.1:2148): java.net.SocketException “socket closed”错误&#xff0c;启动不了Tomcat&#xff0c;在这种时候&#xff0c;网上的解决办法大多都是修改端口的这…

Java 枚举(enum) 详解7种常见的用法

JDK1.5引入了新的类型——枚举。在 Java 中它虽然算个“小”功能&#xff0c;却给我的开发带来了“大”方便。 用法一&#xff1a;常量 在JDK1.5 之前&#xff0c;我们定义常量都是&#xff1a; public static fianl… 。现在好了&#xff0c;有了枚举&#xff0c;可以把相关…

sqoop导入hive时间格式问题解决方案

sqoop导入hive时间格式问题解决方案 从mysql导入数据时,发现时间格式有问题,要么是时间后面多一位零,要么要使用时间戳,还能不能好好玩耍了?! 于是,我就逛论坛&#xff0c;找大神&#xff0c;最终无果&#xff0c;也许这个问题过于简单吧&#xff0c;居然没有大牛讨论。想了好…

Java枚举类型(enum)详解

文章目录理解枚举类型枚举的定义枚举实现原理枚举的常见方法Enum抽象类常见方法编译器生成的Values方法与ValueOf方法枚举与Class对象枚举的进阶用法向enum类添加方法与自定义构造函数关于覆盖enum类方法enum类中定义抽象方法enum类与接口枚举与switch枚举与单例模式EnumMapEnu…

mysql/sqlyog导入txt文件的方法

今天尝试着用sqlyog向mysql中导入数据&#xff0c;用了以下几种&#xff1a; 一、sql载入 格式&#xff1a; LOAD DATA LOCAL INFILE 文件路径 INTO TABLE 表名 FIELDS TERMINATED BY 字段分隔符 LINES TERMINATED BY 行分隔符;直接进去了 代码&#xff1a; LOAD DATA LOCA…

XSS知识总结

XSS基础 跨站脚本&#xff08;英语&#xff1a;Cross-site scripting&#xff0c;通常简称为&#xff1a;XSS&#xff09;是一种网站应用程序的安全漏洞攻击&#xff0c;是代码注入的一种。它允许恶意用户将代码注入到网页上&#xff0c;其他用户在观看网页时就会受到影响。这…

classmethod 继承_让人眼花缭乱的类继承

Python语言的一个优势是简洁易用。是否简洁易用仅仅是Python语言本身的一个话题&#xff0c;但“好消息”是如果你想创造那种一大堆继承、混乱的内部关系的代码&#xff0c;也是可以的&#xff01;今天烦人的代码来自于验证某些math-y数学分析代码。一开始&#xff0c;他们是发…

JAVA SSM框架+Redis 实现单点登录

1&#xff1a;什么是单点登录&#xff1f; 答&#xff1a;单点登录的英文名叫做&#xff1a;Single Sign On&#xff08;简称SSO&#xff09; 一般我们的模块都是在同一个系统下&#xff0c;同一个tomcat&#xff08;如图&#xff0c;以商城为例&#xff09; 后来为了维护和…

2017云栖大会门票转让_「揭秘GP」云栖大会 | Greenplum 6.0 内核优化解读和7.0展望...

9月25日&#xff0c;云栖大会在杭州阿里巴巴云栖小镇正式拉开序幕&#xff0c;三天会议期间&#xff0c;共吸引了200多位世界级科学家、400多家科技合作伙伴参与&#xff0c;科技展区面积超过3万平方米&#xff0c;共发布了1000多项顶尖技术。云栖大会现场在此次云栖大会上&…

从mysql到大数据(一)--开宗明义

一、大数据长什么样 长像很普通&#xff0c;至少看两眼后就觉得很平常。 举个栗子&#xff1a; 一个表格&#xff0c;学生信息表&#xff0c;里面有学号、姓名、性别、年龄、学校、学院、专业、年级、宿舍号等信息如下&#xff0c; 但是表在库里&#xff0c;我们想看&#xf…

SSO单点登录方案大全

分布式微服务系统主流常用的登录方案 前言: 单点登录其实是一个概念,主要是为了解决一次登录,多系统(本系统或外部系统)之间不需要重复登录的问题,就目前来说,主流的解决方案针对业务场景分为3个方向: 1: 同一公司,同父域下的单点登录解决方案. 如[http://map.baidu.com][[h…

em算法怎么对应原有分类_机器学习基础-EM算法

EM算法也称期望最大化(Expectation-Maximum,简称EM)算法&#xff0c;它是一个基础算法&#xff0c;是很多机器学习领域算法的基础&#xff0c;比如隐式马尔科夫算法(HMM)&#xff0c; LDA主题模型的变分推断等等。本文就对EM算法的原理做一个总结。EM算法要解决的问题我们经常会…

postman插件下载、安装教程

这里只讲如何在Chrome 中安装postman插件 下载链接&#xff1a;https://pan.baidu.com/s/1vampHeD0UiDNbrB3G8j_hA 提取码&#xff1a;wqdl 方法/步骤 1.在Chrome输入地址&#xff1a;[chrome://extensions/] 2.将压缩包直接拖拽至Chrome中 3.运行在Chrome输入地址&#xff…

得力条码扫描器怎么用_广东智能物流控制系统怎么选

广东智能物流控制系统怎么选&#xff0c;东莞智库&#xff0c;东莞智库(SmartWarehouse)&#xff0c;专注电子制造SMT智能仓库&#xff0c;致力于帮助电子制造企业提高物流仓储效率和效能。广东智能物流控制系统怎么选&#xff0c; 旭日东自动分拣系统是个集机械、电气、计算机…

从mysql到大数据(三)--mysql数据库建模一常用数据类型及引擎

数据库的安装请自行百度。如果你想直捣黄龙练查询&#xff0c;没有表没有数据是不能实现的。我们从建表开始学习。但要知道&#xff0c;我们所有东西都是了解&#xff0c;学习不要有压力&#xff0c;不要必须要求记什么&#xff0c;当然&#xff0c;如果你不累可以记&#xff0…

Postman用法简介-Http请求模拟工具

Postman用法简介-Http请求模拟工具 在我们平时开发中&#xff0c;特别是需要与接口打交道时&#xff0c;无论是写接口还是用接口&#xff0c;拿到接口后肯定都得提前测试一下&#xff0c;这样的话就非常需要有一个比较给力的Http请求模拟工具&#xff0c;现在流行的这种工具也…

matlab多元函数_函数的计算机处理8(1)_1MATLAB

计算机语言运用--数值计算8-函数的计算机处理8(1)_1MATLAB计算机&#xff1a;电子线路组成的计算机器。人与计算机则是通过计算机语言-符号系统说给计算机听而交流。计算机语言有低级语言-机器语言、汇编、高级语言-C/C/C#/VB/PASCAL/LISP/JAVA/PYTHON/……成百上千种之多。 作…