SpringMVC 源码解析

不羁岁月 提交于 2019-11-28 05:34:51

前言

        年初面试时接触到一道面试题,在聊到SpringMVC时提到了SpringMVC的开发者为何要设计父子容器呢,又或者说是父子容器的设计有什么更实际的作用呢?
         首先要理解对于一个web应用,当期部署在web容器上时,容器会为其提供一个全局上下文环境ServletContext,这个上下文环境将为后续的Spring提供宿主环境。

SpringMVC工作流程

DispatcherServlet上下文继承关系

SpringMVC设计的父子容器

父子容器配置文件

--在web.xml中配置,两个重要的xml:applicationContext.xml和SpringMVC-conf.xml <context-param>     <param-name>contextConfigLocation</param-name>     <param-value>classpath*:applictionContext.xml</param-value> </context-param> <listener>    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>    <servlet>     <servlet-name>dispatcher-servlet</servlet-name>     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>     <init-param>       <param-name>contextConfigLocation</param-name>       <param-value>classpath*:springMVC-conf.xml</param-value>     </init-param> </servlet>    <servlet-mapping>   <servlet-name>dispatcher-servlet</servlet-name>   <url-pattern>/*</url-pattern> </servlet-mapping>

父子容器的设计目的

     根据SpringMVC的官方解释,父(根)容器主要包括一些基础脚手架的bean,比如Pool、DataSource、Dao、Service。目的是在不同的Servlet实例之间共享。这些不同的bean可以在子容器中重写。
     而子容器主要包括一些Controller、View等一些web相关的bean。 

DispatcherServlet源码分析

     既然SpringMVC中同时包含Spring容器和SpringMVC容器,那么这两个容器都是在什么时候初始化呢?

根容器初始化

      首先,根容器是通过ServletContext监听器进行创建,默认的监听器为ContextLoaderListener,当web应用启动时,会调用监听器的contextInitialized方法。
      那么根容器的初始化就从ContextLoaderListener类说起吧,,Spring官方对该类的描述是启动监听器去启动和关闭Spring的root WebApplicationContext(翻译的实在有点蹩脚)。
     

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {     public ContextLoaderListener() {     }      public ContextLoaderListener(WebApplicationContext context) {         super(context);     }      //===初始化root WebApplicationContext===     @Override     public void contextInitialized(ServletContextEvent event) {         initWebApplicationContext(event.getServletContext());     }      @Override     public void contextDestroyed(ServletContextEvent event) {         closeWebApplicationContext(event.getServletContext());         ContextCleanupListener.cleanupAttributes(event.getServletContext());     } }

   

//ContextLoader.java public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {     //初始化Spring容器时如果发现servlet 容器中已存在根Spring容根器则抛出异常,证明rootWebApplicationContext只能有一个。     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!");     }      try {         //创建webApplicationContext实例         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);                 }                 //配置WebApplicationContext                 configureAndRefreshWebApplicationContext(cwac, servletContext);             }         }                  /**            把生成的webApplicationContext设置成root WebApplicationContext。保存在ServletContext上下文中。            下一步初始化MVC ApplicationContext时需要从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);         }                  return this.context;     }     catch (RuntimeException | Error ex) {         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);         throw ex;     } }

      以上代码主要完成两个功能:创建实例WebApplicationContext实例、把所创建的WebApplicationContext设置为根上下文,也就是设置成为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值。

MVC容器初始化 

      大家知道Servlet生命周期都是从init方法开始,desctory方法结束,由jvm负责垃圾回收。而DispatcherServlet也是一个普通的Servlet,先看一下DispatcherServlet的继承关系图,对整个继承关系有个了解。


   既然说起Servlet,那就从Servlet的初始化(init)方法入手
//HttpServletBean.java @Override public final void init() throws ServletException {      PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);     if (!pvs.isEmpty()) {         try {             BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);             ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());             bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));             initBeanWrapper(bw);             bw.setPropertyValues(pvs, true);         }         catch (BeansException ex) {             throw ex;         }     }      //交给子类重写     initServletBean(); }  //FrameworkServlet.java @Override protected final void initServletBean() throws ServletException {     try {         this.webApplicationContext = initWebApplicationContext();         initFrameworkServlet();     }     catch (ServletException | RuntimeException ex) {         throw ex;     } }  //FrameworkServlet.java //初始化MVC容器 protected WebApplicationContext initWebApplicationContext() {     //从ServletContext取出根上下文     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);                 }                 configureAndRefreshWebApplicationContext(cwac);             }         }     }     if (wac == null) {         wac = findWebApplicationContext();     }          //如果还没有webApplicatioinContext,创建webApplicationContext     if (wac == null) {         wac = createWebApplicationContext(rootContext);     }      //子类自定义对servlet子上下文后续操作,在DispatcherServlet中实现     if (!this.refreshEventReceived) {         synchronized (this.onRefreshMonitor) {             //执行子类扩展方法onRefresh,在DispatcherServlet内初始化所有web相关组件             onRefresh(wac);         }     }      //发布servlet子上下文到ServletContext     if (this.publishContext) {         String attrName = getServletContextAttributeName();         //将servlet子上下文以org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletName的属性名称注册到ServletContext中         getServletContext().setAttribute(attrName, wac);     }      return wac; }  protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {     return createWebApplicationContext((ApplicationContext) parent); }  protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {     //获取WebApplicationContext实现类,此处其实就是XmlWebApplicationContext     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");     }          //生成XmlWebApplicationContext实例     ConfigurableWebApplicationContext wac =             (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);      wac.setEnvironment(getEnvironment());     //设置根容器为父容器      wac.setParent(parent);     String configLocation = getContextConfigLocation();     if (configLocation != null) {         //设置配置文件         wac.setConfigLocation(configLocation);     }          //配置webApplicationContext     configureAndRefreshWebApplicationContext(wac);      return wac; }  protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {     if (ObjectUtils.identityToString(wac).equals(wac.getId())) {         if (this.contextId != null) {             wac.setId(this.contextId);         }         else {             wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());         }     }      wac.setServletContext(getServletContext());     wac.setServletConfig(getServletConfig());     wac.setNamespace(getNamespace());     wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));      ConfigurableEnvironment env = wac.getEnvironment();     if (env instanceof ConfigurableWebEnvironment) {         ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());     }      postProcessWebApplicationContext(wac);     applyInitializers(wac);          //开始处理bean     wac.refresh(); }

      上面的关键代码都在FrameworkServlet类中,有几个关键点:取除根上下文,创建子上下文并设置父上下文,完成刷新,把子上下文发布到ServletContext中。 到这里可以说子容器(子上下文)已经创建完成。 并把其他初始化web组件的相关工作交给onRefresh方法完成,由DispatcherServlet来重写onRefresh方法,这就又回到了我们熟悉的initStrategies方法。

web组件初始化

 

@Override protected void onRefresh(ApplicationContext context) {     initStrategies(context); }  protected void initStrategies(ApplicationContext context) {     //文件上传解析器     initMultipartResolver(context);          //本地化解析器     initLocaleResolver(context);          //主题解析器     initThemeResolver(context);          //处理器映射器(url和Controller方法的映射)     initHandlerMappings(context);          //处理器适配器(实际执行Controller方法)     initHandlerAdapters(context);          //处理器异常解析器     initHandlerExceptionResolvers(context);          //RequestToViewName解析器     initRequestToViewNameTranslator(context);          //视图解析器(视图的匹配和渲染)     initViewResolvers(context);          //FlashMap管理者     initFlashMapManager(context); }

             这里我们主要关注一下三个重要组件:HandlerMapping、HandlerAdapter、ViewResolver。分析这3个组件之前,我们先看一下我们的springMVC-conf.xml配置文件,mvc的配置文件中,我们配置了两行代码:

<context:component-scan base-package="com.zhangfei"/> <mvc:annotation-driven>

      第二行代码主要是添加了默认的HandleMapping,ViewResolver,HandleAdapter。我们看看annotation-driven的源码定义,根据spring自定义schema定义,我们找到如下代码,如图所示:


该文件就一行代码:
http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
//MVC所有的标签解析器都定义在此 public class MvcNamespaceHandler extends NamespaceHandlerSupport {     @Override     public void init() {         registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());         registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());         registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());         registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());         registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());         registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());         registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());         registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());         registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());         registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());         registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());         registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());         registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());     } }

那么通过分析AnnotationDrivenBeanDefinitionParser类,主要完成以下三大组件的装配工作:

初始化处理器映射器

private void initHandlerMappings(ApplicationContext context) {     this.handlerMappings = null;      //这里detectAllHandlerMappings默认值为true,可以通过配置文件设置为false     if (this.detectAllHandlerMappings) {         //从上下文(包含父上下文)中查找所有HandlerMapping实现类         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 {             //这里只取固定的bean             HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);             this.handlerMappings = Collections.singletonList(hm);         }         catch (NoSuchBeanDefinitionException ex) {                      }     }          /***       确保至少有一个HandlerMapping,如果没能找到,注册一个默认的       默认规则在DispatcherServlet.properties中,这里也就是取BeanNameUrlHandlerMapping、RequestMappingHandlerMapping     ***/     if (this.handlerMappings == null) {         this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);          } }

初始化处理器适配器

private void initHandlerAdapters(ApplicationContext context) {     this.handlerAdapters = null;      if (this.detectAllHandlerAdapters) {         //从上下文(包括父上下文)中查找所有HandlerAdapter实现类         Map<String, HandlerAdapter> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);         if (!matchingBeans.isEmpty()) {             this.handlerAdapters = new ArrayList<>(matchingBeans.values());             AnnotationAwareOrderComparator.sort(this.handlerAdapters);         }     }     else {         try {             //这里取bean名字为handlerAdapter,类型为HandlerAdapter的处理器适配器             HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);             this.handlerAdapters = Collections.singletonList(ha);         }         catch (NoSuchBeanDefinitionException ex) {                      }     }          /**     如果没找到,则从默认规则里取出指定的三个实现类:HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、RequestMappingHandlerAdapter     **/     if (this.handlerAdapters == null) {         this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);          } }

初始化试图解析器

private void initViewResolvers(ApplicationContext context) {     this.viewResolvers = null;      if (this.detectAllViewResolvers) {         //从上下文(包括父上下文)中查找所有ViewResolver实现类         Map<String, ViewResolver> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);         if (!matchingBeans.isEmpty()) {             this.viewResolvers = new ArrayList<>(matchingBeans.values());             AnnotationAwareOrderComparator.sort(this.viewResolvers);         }     }     else {         try {             ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);             this.viewResolvers = Collections.singletonList(vr);         }         catch (NoSuchBeanDefinitionException ex) {                      }     }          /**     如果没找到,则从默认规则里取出指定的实现类:InternalResourceViewResolver     **/     if (this.viewResolvers == null) {         this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);      } }

         三大组件的初始化最后判断为NULL时都会调用getDefaultStrategies方法,也就是从DispatcherServlet.properties中取出指定默认值。

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<?> 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<>();     } }

DispatcherServlet请求处理过程

     提到请求处理过程,我们再来回顾一下Servlet生命周期,处理请求都放在service方法中处理,那么也从DispatcherServlet的service方法入手。DispatcherServlet继承FrameworkServlet,在FrameworkServlet中重写了service、doGet、doPost、doPut、doDelete方法。

//FrameworkServlet.java @Override protected void service(HttpServletRequest request, HttpServletResponse response)         throws ServletException, IOException {     HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());     if (httpMethod == HttpMethod.PATCH || httpMethod == null) {         processRequest(request, response);     }     else {         super.service(request, response);     } }       @Override protected final void doGet(HttpServletRequest request, HttpServletResponse response)         throws ServletException, IOException {     processRequest(request, response); }       @Override protected final void doPost(HttpServletRequest request, HttpServletResponse response)         throws ServletException, IOException {     processRequest(request, response); }       @Override protected final void doPut(HttpServletRequest request, HttpServletResponse response)         throws ServletException, IOException {     processRequest(request, response); }       @Override protected final void doDelete(HttpServletRequest request, HttpServletResponse response)         throws ServletException, IOException {     processRequest(request, response); }   protected final void processRequest(HttpServletRequest request, HttpServletResponse response)             throws ServletException, IOException {      long startTime = System.currentTimeMillis();     Throwable failureCause = null;      LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();     LocaleContext localeContext = buildLocaleContext(request);      RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();     ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);      WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);     asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());          //把新构造的LocaleContext对象和ServletRequestAttributes对象和当前请求线程绑定(后面要解除绑定)     initContextHolders(request, localeContext, requestAttributes);      try {         //抽象方法,交给DispatcherServlet方法实现         doService(request, response);     }     catch (ServletException | IOException ex) {         failureCause = ex;         throw ex;     }     catch (Throwable ex) {         failureCause = ex;         throw new NestedServletException("Request processing failed", ex);     }      finally {         //重置LocaleContext和RequestAttributes对象,也就是解除LocaleContext对象和ServletRequestAttributes对象和当前请求线程的绑定         resetContextHolders(request, previousLocaleContext, previousAttributes);         if (requestAttributes != null) {             requestAttributes.requestCompleted();         }         //发布ServletRequestHandledEvent事件         publishRequestHandledEvent(request, response, startTime, failureCause);     } }
//DispatcherServlet.java @Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {          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));             }         }     }          //在当前request对象中填充4个属性     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()) {             if (attributesSnapshot != null) {                 restoreAttributesAfterInclude(request, attributesSnapshot);             }         }     } }   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);                          //调用handlerMapping获取handlerChain             mappedHandler = getHandler(processedRequest);             if (mappedHandler == null) {                 noHandlerFound(processedRequest, response);                 return;             }              //获取支持该handler解析的HandlerAdapter             HandlerAdapter 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;             }              //使用HandlerAdapter完成handler处理             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);             }         }     } }

        DispatcherServlet的doDispatch方法概括起来大致就是以下几点:首先根据当前请求路径找到对应的HandlerMethod,一个HandlerMethod和若干个拦截器构造一个HandlerExecutionChain.通过HandlerExecutionChain得到HandlerAdapter对象通过执行HandlerAdapter的handle方法得到ModelAndView对象,调用ModelAndView解析视图,渲染视图,Response结束。

参考

https://juejin.im/post/5cb89dae6fb9a0686b47306d
https://juejin.im/post/5cbc10b46fb9a0689f4c2c22
https://www.cnblogs.com/fangjian0423/p/springMVC-dispatcherServlet.html

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!