SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门

简介:
刚接触SpringMVC,对它的xml文件配置一直比较模模糊糊,最近花了一点时间稍微看了下源代码,再加上调试,开始逐渐理解它,网上的类似的内容有很多,写本文主要是自己加深一下理解。本文适合用过SpringMVC的开发者,言归正传,首先搭建一个最简单的工程体验一下。 

该工程是基于maven的,pom配置不再说明,所使用的spring版本4.0.5。 
首先是web.xml文件配置,最简单的配置 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE web-app PUBLIC
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 
<web-app>
   <display-name>Archetype Created Web Application</display-name>
   <servlet>
         <servlet-name>mvc</servlet-name>
         <servlet- class >org.springframework.web.servlet.DispatcherServlet</servlet- class >
         <load-on-startup> 1 </load-on-startup>
     </servlet>
 
     <servlet-mapping>
         <servlet-name>mvc</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
</web-app>

然后是mvc-servlet.xml文件的配置,上面配置DispatcherServlet会默认加载[servlet-name]-servlet.xml文件。对于我的配置,会去加载mvc-servlet.xml文件。 
mvc-servlet.xml文件的内容:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version= "1.0" encoding= "UTF-8" ?>
     xsi:schemaLocation="http: //www.springframework.org/schema/beans
     http: //www.springframework.org/schema/beans/spring-beans-3.1.xsd
     http: //www.springframework.org/schema/mvc
     http: //www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
     http: //www.springframework.org/schema/util
     http: //www.springframework.org/schema/util/spring-util-2.0.xsd
     http: //www.springframework.org/schema/context
     http: //www.springframework.org/schema/context/spring-context-3.2.xsd">
 
     <bean name= "/index" class = "com.lg.mvc.HomeAction" ></bean>
     <bean class = "org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer" >
         <property name= "templateLoaderPath" value= "/WEB-INF/views" />
         <property name= "defaultEncoding" value= "utf-8" />
         <property name= "freemarkerSettings" >
             <props>
                 <prop key= "locale" >zh_CN</prop>
             </props>
         </property>
     </bean>
     <bean class = "org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver" >
         <property name= "suffix" value= ".html" />
         <property name= "contentType" value= "text/html;charset=utf-8" />
         <property name= "requestContextAttribute" value= "request" />
         <property name= "exposeRequestAttributes" value= "true" />
         <property name= "exposeSessionAttributes" value= "true" />
     </bean>
</beans>

在该配置中定义了一个HomeAction的Bean。内容为:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.lg.mvc;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
 
public class HomeAction implements Controller{
 
     @Override
     public ModelAndView handleRequest(HttpServletRequest request,
             HttpServletResponse response) throws Exception {
         return new ModelAndView( "hello" );
     }
}

这是最原始的mvc做法,要继承Controller接口,先从原始的说起,最后再过渡到@Controller和@RequestMapping注解式的配置。它在mvc-serlet.xml文件中的配置有一个关键的属性name="/index"。 
WEB-INF/view目录下有一个简单的hello.html,内容为:
 
?
1
2
3
4
5
6
7
8
<html>
     <head>
     
     </head>
     <body>
         hello lg !
     </body>
</html>

至此该工程就写完了,部署到tomcat中,项目路径为/,运行一下。 
访问 http://localhost:8080/index 
 
至此整个工程就算搭建成功了。 

下面就要说说原理了。 
用过python Django框架的都知道Django对于访问方式的配置就是,一个url路径和一个函数配对,你访问这个url,就会直接调用这个函数,简单明了。对于java的面向对象来说,就要分两步走。第一步首先要找到是哪个对象,即handler,本工程的handler则是HomeAction对象。第二步要找到访问的函数,即HomeAction的handleRequest方法。所以就出现了两个源码接口 HandlerMapping和HandlerAdapter,前者负责第一步,后者负责第二步。借用网上的SpringMVC架构图。 
 
HandlerMapping接口的实现(只举了我认识的几个) : 

    BeanNameUrlHandlerMapping :通过对比url和bean的name找到对应的对象 
    SimpleUrlHandlerMapping :也是直接配置url和对应bean,比BeanNameUrlHandlerMapping功能更多 
    DefaultAnnotationHandlerMapping : 主要是针对注解配置@RequestMapping的,已过时 
    RequestMappingHandlerMapping :取代了上面一个 

HandlerAdapter 接口实现: 

    HttpRequestHandlerAdapter : 要求handler实现HttpRequestHandler接口,该接口的方法为                                                             void handleRequest(HttpServletRequest request, HttpServletResponse response)也就是  handler必须有一个handleRequest方法 

    SimpleControllerHandlerAdapter:要求handler实现Controller接口,该接口的方法为ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response),也就是本工程采用的 

    AnnotationMethodHandlerAdapter :和上面的DefaultAnnotationHandlerMapping配对使用的,也已过时 

    RequestMappingHandlerAdapter : 和上面的RequestMappingHandlerMapping配对使用,针对@RequestMapping 

先简单的说下这个工程的流程,访问http://localhost:8080/index首先由DispatcherServlet进行转发,通过BeanNameUrlHandlerMapping(含有 /index->HomeAction的配置),找到了HomeAction,然后再拿HomeAction和每个adapter进行适配,由于HomeAction实现了Controller接口,所以最终会有SimpleControllerHandlerAdapter来完成对HomeAction的handleRequest方法的调度。然后就顺利的执行了我们想要的方法,后面的内容不在本节中说明。 

了解了大概流程,然后就需要看源代码了。 
首先就是SpringMVC的入口类,DispatcherServlet,它实现了Servlet接口,不再详细说DispatcherServlet的细节,不然又是一大堆的内容。每次请求都会调用它的doService->doDispatch,我们关注的重点就在doDispatch方法中。
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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找到对应的handler
                 // Determine handler for the current request.
                 mappedHandler = getHandler(processedRequest);
                 if (mappedHandler == null || mappedHandler.getHandler() == null ) {
                     noHandlerFound(processedRequest, response);
                     return ;
                 }
 
                 // Determine handler adapter for the current request.
                        //这是第二步,找到合适的HandlerAdapter,然后由它来调度执行handler的方法
                 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
 
                 // Process last-modified header, if supported by the handler.
                 String method = request.getMethod();
                 boolean isGet = "GET" .equals(method);
                 if (isGet || "HEAD" .equals(method)) {
                     long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                     if (logger.isDebugEnabled()) {
                         logger.debug( "Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                     }
                     if ( new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                         return ;
                     }
                 }
 
                 if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                     return ;
                 }
 
                 try {
                     // Actually invoke the handler.
                     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                 }
                 finally {
                     if (asyncManager.isConcurrentHandlingStarted()) {
                         return ;
                     }
                 }
 
                 applyDefaultViewName(request, mv);
                 mappedHandler.applyPostHandle(processedRequest, response, mv);
             }
             catch (Exception ex) {
                 dispatchException = ex;
             }
             processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
         }
         catch (Exception ex) {
             triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
         }
         catch (Error err) {
             triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
         }
         finally {
             if (asyncManager.isConcurrentHandlingStarted()) {
                 // Instead of postHandle and afterCompletion
                 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                 return ;
             }
             // Clean up any resources used by a multipart request.
             if (multipartRequestParsed) {
                 cleanupMultipart(processedRequest);
             }
         }
     }

第一步详细查看:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
         for (HandlerMapping hm : this .handlerMappings) {
             if (logger.isTraceEnabled()) {
                 logger.trace(
                         "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'" );
             }
             HandlerExecutionChain handler = hm.getHandler(request);
             if (handler != null ) {
                 return handler;
             }
         }
         return null ;
     }

可以看到就是通过遍历所有已注册的HandlerMapping来找到对应的handler,然后构建出一个HandlerExecutionChain,它包含了handler和HandlerMapping本身的一些拦截器,如下  
?
1
2
3
4
5
6
7
8
9
10
public class HandlerExecutionChain {
 
     private final Object handler;
 
     private HandlerInterceptor[] interceptors;
 
     private List<HandlerInterceptor> interceptorList;
         
         //其他代码省略
}

其中HandlerMapping的getHandler实现:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
         Object handler = getHandlerInternal(request);
         if (handler == null ) {
             handler = getDefaultHandler();
         }
         if (handler == null ) {
             return null ;
         }
         // Bean name or resolved handler?
         if (handler instanceof String) {
             String handlerName = (String) handler;
             handler = getApplicationContext().getBean(handlerName);
         }
         return getHandlerExecutionChain(handler, request);
     }

这里的getHandlerInternal(request)是个抽象方法,由具体的HandlerMapping来实现,获取到的handler如果为空,则获取默认配置的handler,如果handler为String类型,则表示这个则会去Spring容器里面去找这样名字的bean。 
再看下BeanNameUrlHandlerMapping的getHandlerInternal(request)的具体实现(通过一系列的接口设计,之后再好好看看这个设计,到BeanNameUrlHandlerMapping这只用实现该方法中的一部分),如下
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
 
     /**
      * Checks name and aliases of the given bean for URLs, starting with "/".
      */
     @Override
     protected String[] determineUrlsForHandler(String beanName) {
         List<String> urls = new ArrayList<String>();
         if (beanName.startsWith( "/" )) {
             urls.add(beanName);
         }
         String[] aliases = getApplicationContext().getAliases(beanName);
         for (String alias : aliases) {
             if (alias.startsWith( "/" )) {
                 urls.add(alias);
             }
         }
         return StringUtils.toStringArray(urls);
     }
 
}

这里面注释说,bean的name必须以/开头,它才处理,将信息存储在Map<String, Object> handlerMap中,对于本工程来说就是{'/index':HomeAction对象}。 
至此这里完成了第一步,下面开始第二步,即方法HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());的具体实现:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
         for (HandlerAdapter ha : this .handlerAdapters) {
             if (logger.isTraceEnabled()) {
                 logger.trace( "Testing handler adapter [" + ha + "]" );
             }
             if (ha.supports(handler)) {
                 return ha;
             }
         }
         throw new ServletException( "No adapter for handler [" + handler +
                 "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler" );
     }

遍历所有的HandlerAdapter,判断他们是否支持这个handler。 
我们来看下HttpRequestHandlerAdapter的supports(handler)方法:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class HttpRequestHandlerAdapter implements HandlerAdapter {
 
     @Override
     public boolean supports(Object handler) {
           //就是判断handler是否实现了HttpRequestHandler接口
         return (handler instanceof HttpRequestHandler);
     }
 
     @Override
     public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
             throws Exception {
            //若handler实现了HttpRequestHandler接口,则调用该接口的方法,执行我们在该方法中写的业务逻辑
         ((HttpRequestHandler) handler).handleRequest(request, response);
         return null ;
     }
 
     @Override
     public long getLastModified(HttpServletRequest request, Object handler) {
         if (handler instanceof LastModified) {
             return ((LastModified) handler).getLastModified(request);
         }
         return -1L;
     }
 
}

同理SimpleControllerHandlerAdapter也是这样类似的逻辑  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
 
     @Override
     public boolean supports(Object handler) {
         return (handler instanceof Controller);
     }
 
     @Override
     public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
             throws Exception {
 
         return ((Controller) handler).handleRequest(request, response);
     }
 
     @Override
     public long getLastModified(HttpServletRequest request, Object handler) {
         if (handler instanceof LastModified) {
             return ((LastModified) handler).getLastModified(request);
         }
         return -1L;
     }
 
}

剩余两个AnnotationMethodHandlerAdapter和RequestMappingHandlerAdapter就比较复杂,我也没看。 
按照本工程的配置,则SimpleControllerHandlerAdapter是支持HomeAction的,然后就会执行SimpleControllerHandlerAdapter的handle(processedRequest, response, mappedHandler.getHandler())方法。本质上就会调用HomeAction实现Controller接口的方法。至此就分析完了。 
了解过程了之后,然后就是最重要的也是经常配置出问题的地方。DispatcherServlet的handlerMappings和handlerAdapters的来源问题。 

DispatcherServlet初始化的时候,会调用一个方法如下: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
protected void initStrategies(ApplicationContext context) {
         initMultipartResolver(context);
         initLocaleResolver(context);
         initThemeResolver(context);
//初始化一些HandlerMapping
         initHandlerMappings(context);
//初始化一些HandlerAdapter
         initHandlerAdapters(context);
         initHandlerExceptionResolvers(context);
         initRequestToViewNameTranslator(context);
         initViewResolvers(context);
         initFlashMapManager(context);
     }

这里可以看到,它会初始化一些HandlerMapping和HandlerAdapter,这两个方法非常重要,理解了这两个方法你就会知道,配置不对问题出在哪里,下面具体看下这两个方法:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void initHandlerMappings(ApplicationContext context) {
         this .handlerMappings = null ;
 
         if ( this .detectAllHandlerMappings) {
             // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
             Map<String, HandlerMapping> matchingBeans =
                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping. class , true , false );
             if (!matchingBeans.isEmpty()) {
                 this .handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                 // We keep HandlerMappings in sorted order.
                 OrderComparator.sort( this .handlerMappings);
             }
         }
         else {
             try {
                 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.
             }
         }
 
         // Ensure we have at least one HandlerMapping, by registering
         // a default HandlerMapping if no other mappings are found.
         if ( this .handlerMappings == null ) {
             this .handlerMappings = getDefaultStrategies(context, HandlerMapping. class );
             if (logger.isDebugEnabled()) {
                 logger.debug( "No HandlerMappings found in servlet '" + getServletName() + "': using default" );
             }
         }
     }

detectAllHandlerMappings是DispatcherServlet的一个属性,你是可以在web.xml中配置的,默认是true,如果为true,则会去从本工程mvc-servlet.xml文件中去探测所有实现了HandlerMapping的bean,如果有,则加入DispatcherServlet的handlerMappings中。如果detectAllHandlerMappings为false,则直接去容器中找id="handlerMapping"且实现了HandlerMapping的bean.如果以上都没找到,则会去加载默认的HandlerMapping。  
?
1
2
/** Detect all HandlerMappings or just expect "handlerMapping" bean? */
     private boolean detectAllHandlerMappings = true ;

本工程由于没有配置HandlerMapping,所以它会去加载默认的,下面看看默认的配置是什么  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
         String key = strategyInterface.getName();
//defaultStrategies存储了默认的配置
         String value = defaultStrategies.getProperty(key);
         if (value != null ) {
             String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
             List<T> strategies = new ArrayList<T>(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(
                             "Error loading DispatcherServlet's default strategy class [" + className +
                                     "] for interface [" + key + "]: problem with class file or dependent class" , err);
                 }
             }
             return strategies;
         }
         else {
             return new LinkedList<T>();
         }
     }

继续看看defaultStrategies是如何初始化的:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static final Properties defaultStrategies;
 
     static {
         // Load default strategy implementations from properties file.
         // This is currently strictly internal and not meant to be customized
         // by application developers.
         try {
//这里的DEFAULT_STRATEGIES_PATH就是DispatcherServlet.properties
             ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet. class );
             defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
         }
         catch (IOException ex) {
             throw new IllegalStateException( "Could not load 'DispatcherServlet.properties': " + ex.getMessage());
         }
     }

这里使用静态代码块来加载配置文件DispatcherServlet.properties,它所在位置就是和DispatcherServlet同一目录下面的,如下图所示: 

 
该默认的配置文件的内容如下 : 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
 
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
 
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
 
#这里就是默认的HandlerMapping的配置
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
     org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
#这里就是默认的HandlerAdapter的配置
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
     org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
     org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
 
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
     org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
     org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
 
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
 
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
 
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

也就是说,当你什么都没有配置时,默认会加载以上的配置。正是由于有了上述默认配置的BeanNameUrlHandlerMapping(它要求name必须是以/开头的),它才会存储我们在mvc-servlet.xml中配置的<bean name="/index" class="com.lg.mvc.HomeAction"></bean>,同样正是由于有了SimpleControllerHandlerAdapter(由于handler实现了Controller接口,所以它的support方法支持我们的handler),才会调度执行HomeAction的handleRequest方法。
相关文章
|
6月前
|
Java 应用服务中间件 Spring
Spring5源码(50)-SpringMVC源码阅读环境搭建
Spring5源码(50)-SpringMVC源码阅读环境搭建
76 0
|
3月前
|
前端开发 Java Spring
SpringMVC种通过追踪源码查看是哪种类型的视图渲染器(一般流程方法)
这篇文章通过示例代码展示了如何在Spring MVC中编写和注册拦截器,以及如何在拦截器的不同阶段添加业务逻辑。
SpringMVC种通过追踪源码查看是哪种类型的视图渲染器(一般流程方法)
|
存储 前端开发 搜索推荐
(八)Spring源码解析:Spring MVC
(八)Spring源码解析:Spring MVC
95 1
|
6月前
|
Java 应用服务中间件 数据库连接
Spring5源码(51)-Servlet知识点回顾以及SpringMVC分析入口
Spring5源码(51)-Servlet知识点回顾以及SpringMVC分析入口
65 0
|
6月前
|
SQL JSON 前端开发
【源码免费下载】SpringBoot整合Spring+SpringMVC+MyBatisPlus案例:图书管理系统
【源码免费下载】SpringBoot整合Spring+SpringMVC+MyBatisPlus案例:图书管理系统
107 0
|
6月前
|
设计模式 前端开发 Java
[Spring ~源码] Spring的run方法以及SpringMVC执行流程
[Spring ~源码] Spring的run方法以及SpringMVC执行流程
|
前端开发 Java Spring
源码浅析SpringMVC请求的流转过程
Spring MVC框架使用了其”模型-视图-控制器”( Model-View-Controller )架构方式,用于开发灵活且松散耦合的 Web 应用程序。我们都使用过SpringMVC来处理信息,并渲染视图到Browser。但需要注意的是,在现在的架构中,大都采用了前后端分离的情况,而我们在使用SpringMVC的时候,只需要关注M(Model),C(Controller)这两个部分,而视图渲染的部分则交给了前端。
322 0
源码浅析SpringMVC请求的流转过程
|
Java Spring 容器
享读SpringMVC源码5-异常处理HandlerExceptionResolver(上)
享读SpringMVC源码5-异常处理HandlerExceptionResolver(上)
享读SpringMVC源码5-异常处理HandlerExceptionResolver(上)
|
Java Spring
享读SpringMVC源码4-感谢RequestMappingHandlerAdapter(下)
享读SpringMVC源码4-感谢RequestMappingHandlerAdapter(下)
享读SpringMVC源码4-感谢RequestMappingHandlerAdapter(下)
|
应用服务中间件
享读SpringMVC源码0--从Tomcat到SpringMVC
享读SpringMVC源码0--从Tomcat到SpringMVC
享读SpringMVC源码0--从Tomcat到SpringMVC