SpringMVC简介
Java行业的谁人不知SSM框架呢?除非你告诉我刚学Java,我就相信你不知道SpringMVC。
关于SringMVC的由来和干嘛用的基本都不用介绍了,基本都知道了。但是有一点可以肯定的是:有很多人只停留在SpringMVC使用层面,对于SpringMVC的底层原理和源码却没有深入了解过。
这一期我们就来了解「SpringMVC的底层原理和源码」,在以前的JSP时代,代码中前端和后端都混在一起,可能比较老的程序员就写过下面的代码,这就是大名鼎鼎的JSP
和Servlet
时代。
在一些老的项目中可能就会出现这样的代码,这样的代码是不是看起来非常的带劲,要是让你维护这样的代码,想死的心都有。
这样的代码前端和后端混在一起,相互依赖JSP与Java Bean之间严重耦合,java代码和Html代码混在一起,这要求开发人员既要会前端也要会后端,给测试带来了很多不方便,代码也不能复用。
诸如此类的问题,为了解决这样的问题,首先就是将这些代码进行严格的划分,前端与后端的代码分开,逐渐出现代码的分层架构,各层职责分明。
![](https://i-blog.csdnimg.cn/blog_migrate/5eded17dce81b1e892b4c68d4481fc1f.png)
但是,这样的模型层也还会有问题,首先每个模块就需要一个Servlet控制器,模块多的,控制器就会变得很多,这样会导致控制器复杂。
并且更换视图技术麻烦,严重依赖Servlet API。Java Bean结构包含持久化层以及业务的处理,数据的封装,这样就会导致Java Bean结构臃肿。
按照我们现在代码的分层,可以把Java Bean又分为「持久层(dao)和服务层(Service)」 以及我们的 「应用控制层(Controller)」。
![](https://i-blog.csdnimg.cn/blog_migrate/38efe57e82dabe21cef36d5408a6151e.png)
SpringMVC原理
为了简化控制层(Servlet),在SpringMVC
框架中使用「DispatcherServlet(前端控制器)」 调度我们自己的「应用控制层(Controller)」。
就这样逐渐的演变,出现了我们现在真正意义上的Web MVC三层架构,具体的结构图如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/47a68e20a7b6f013eb0f29d7bb48e4a5.png)
首先来说明一下SpringMVC几个核心的组件:
DispatcherServlet
:前端前端控制器主要负责调度工作,进行全局的流程控制。比如:调度HandlerMapping然后返回执行链。HandlerMapping
:处理器映射器会返回一个执行链,通俗来讲也就是执行的逻辑顺序,执行链中包含多个「Interceptor(拦截器)」 和一个「Handler(处理器)」。HandlerAdapter
:处理器适配器里面包含了处理器的调用,使用适配器的设计原则,通过反射调用我们自己的Controller。Handler
:处理器也就是我们的Controller,用户对应的请求URL请求过来,通过请求与我们Controller的映射规则(HandlerMapping)相对应起来,这个就是处理器。ModelAndView
:模型和视图,模式(Model)也就是我们的数据,通过上面反射调用Handler(Controller)
生成的数据,以及逻辑视图(View)。逻辑视图并不是真正的视图名,它只是一个逻辑视图名,比如:index。View
:视图,这时候才会通过上面生成的逻辑视图名生成对应的物理视图,返回前端呈现用户。
Hello World
上面说了那么多,其实还是要在项目进行实践中才会有深刻的体会,下面我们通过实际一个案例进行上面的深刻的理解。
这里我使用idea搭建SSM项目,还用Eclipse的同学,建议你该换工具了,首先New - Project
然后左边选择
Maven
,右边勾选Create from archetype
,并且选择webapp
模块:
![](https://i-blog.csdnimg.cn/blog_migrate/b2cc33b77faee72f6c2e249069d776d5.png)
下面就是填写一些GroupId
以及ArtifactId
,这些比较简单就直接跳过了,不然会被大佬diss死了,创建完项目后的基本目录结构如下:
![](https://i-blog.csdnimg.cn/blog_migrate/baedb336e92d74f19de6b23e6f86db5d.png)
并且在resource目录下分别创建下面四个配置文件「applicationContext.xml、jdbc.properties、log4j.properties、spring-mvc.xml」。
applicationContext.xml
是Spring的核心配置文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加载properties文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"/>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
<!-- mybatis和spring完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 扫描model包 -->
<property name="typeAliasesPackage" value="com.ldc.model"/>
<!-- 扫描sql配置文件:mapper需要的xml文件 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!-- Mapper动态代理开发,扫描dao接口包-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫描Dao接口包 -->
<property name="basePackage" value="com.ldc.dao"/>
</bean>
<!-- 事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
这个文件主要配置了数据源、数据库的来连接的配置信息,数据的详细信息就放在jdbc.properties
中:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=user
initialSize=0
maxActive=20
maxIdle=20
minIdle=1
maxWait=60000
这里的数据库信息,你们只要修改数据库的用户名了密码就行了,其它的作为测试信息基本就不用修改了。
接下来就是日志的配置信息log4j.properties
,这里只做简单的日志配置:
#日志输出级别
log4j.rootLogger=debug,stdout,D,E
#设置stdout的日志输出控制台
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#输出日志到控制台的方式,默认为System.out
log4j.appender.stdout.Target = System.out
#设置使用灵活布局
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#灵活定义输出格式
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} -[%p] method:[%c (%rms)] - %m%n
配置完日志信息后,接着配置spring-mvc.xml
,这个的SpringMVC框架内的信息配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- 扫描注解,这样com.ldc包下的文件都能被扫描 -->
<context:component-scan base-package="com.ldc"/>
<!-- 开启SpringMVC注解模式 -->
<mvc:annotation-driven/>
<!-- 静态资源默认servlet配置 -->
<mvc:default-servlet-handler/>
<!-- 配置返回视图的路径,以及识别后缀是jsp文件 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
这和配置信息也很简单,主要包括「开启注解驱动、包扫描、视图解析器的配置」。
配置完SpringMVC后,最后就是配置web.xml,web.xml
是前端请求的入口文件:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>mvcDemo</display-name>
<!--项目的欢迎页,项目运行起来后访问的页面-->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 注册ServletContext监听器,创建容器对象,并且将ApplicationContext对象放到Application域中 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 指定spring核心配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 解决乱码的过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定配置文件位置和名称 如果不设置,默认找/WEB-INF/<servlet-name>-servlet.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
在web.xml中主要包含:「默认欢迎页面的配置、字符编码过滤器的配置、前端控制器、以及指定spring核心配置文件和SpringMVC的配置文件」。
以上就是最基本的配置,其它的配置信息一般是按需配置,这样配置完后,我们搭建一个简单的SSM的项目基本已经完成了。
最后的Maven的坐标依赖,如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ldc</groupId>
<artifactId>mvcDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mvcDemo Maven Webapp</name>
<!-- 用来设置版本号 -->
<properties>
<srping.version>4.0.2.RELEASE</srping.version>
<mybatis.version>3.2.8</mybatis.version>
<slf4j.version>1.7.12</slf4j.version>
<log4j.version>1.2.17</log4j.version>
<druid.version>1.0.9</druid.version>
</properties>
<!-- 用到的jar包 -->
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<!-- 表示开发的时候引入,发布的时候不会加载此包 -->
<scope>test</scope>
</dependency>
<!-- spring框架包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${srping.version}</version>
</dependency>
<!-- spring框架包 -->
<!-- mybatis框架包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
<!-- mybatis框架包 -->
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<!-- 导入dbcp的jar包,用来在applicationContext.xml中配置数据库 -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- jstl标签类 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- log -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>
<build>
<!-- java编译插件,如果maven的设置里配置好jdk版本就不用 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
maven坐标中主要开发包含的依赖有数据库驱动、日志、mybaties、spring坐标、web mvc的坐标、以及继承JSP的坐标。
这里前端技术可以继承你们自己想要的:Freemarker或者Thymeleaf,只需要引入相关的Maven坐标,因为JSP已经基本被淘汰了,这里只是为了作测试,并不关心前端用什么技术。
我们在controller包下创建我们自己的测试类:UserController
:
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@RequestMapping("/getUserById/{id}")
public ModelAndView selectUser(@PathVariable("id") Long id) throws Exception {
ModelAndView mv = new ModelAndView();
User user = userService.selectUser(id);
mv.addObject("user", user);
mv.setViewName("user");
return mv;
}
}
这里简单解释一下:
@Controller:标名它是一个控制器,被Spring容器所管理,这个注解是在@Component后面出的,为了表示代码的分层,于是就有了@Controller、@Service、@Mapper这三个注解,他们的作用是一样的。
@RequestMapping:表示接受的请求,还是GetMapping、PostMapping等注解表示请求方法的不同。
@Autowired:表示自动注入,前提就是被注入的对象被Spring容器所管理。
ModelAndView:这个前面说过,它装的就是数据和逻辑视图名。
这些还是比较简单的,通过下面配置Tomcat信息进行部署,就可以启动项目进行测试了:
![](https://i-blog.csdnimg.cn/blog_migrate/10e3451badb102e4a5cbb0943e129c05.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1024a260c7ee8e26d01d4e2d4261e410.png)
DispatcherServlet源码解析
这个还是比较简单的,还不会可以自行百度,启动项目后我们来测试一下前面,出现下面的界面说明,你搭建SSM项目的基本环境已经成功了:
![](https://i-blog.csdnimg.cn/blog_migrate/6e177d96e79f9cb21401173b59648c00.png)
那么我们的前端请求是怎么一步一步的从「前台->后台->前台」的呢?其实前面我们已经说了SpringMVC的基本原理,在这个基本原理的基础上,从源码的角度,进行详细的解析:
上面说到SpringMVC的核心调度器就是DispatcherServlet
,负责主流程的调度工作,在DispatcherServlet
里面最主要的方法就是doDispatch
:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
try {
ModelAndView mv;
boolean errorView = false;
try {
//检查是否是请求是否是multipart(如文件上传),如果是将通过MultipartResolver解析
processedRequest = checkMultipart(request);
//步骤2、请求到处理器(页面控制器)的映射,通过HandlerMapping进行映射
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
//步骤3、处理器适配,即将我们的处理器包装成相应的适配器(从而支持多种类型的处理器)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 304 Not Modified缓存支持
//此处省略具体代码
// 执行处理器相关的拦截器的预处理(HandlerInterceptor.preHandle)
//此处省略具体代码
// 步骤4、由适配器执行处理器(调用处理器相应功能处理方法)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
// 执行处理器相关的拦截器的后处理(HandlerInterceptor.postHandle)
//此处省略具体代码
}
catch (ModelAndViewDefiningException ex) {
logger.debug("ModelAndViewDefiningException encountered", ex);
mv = ex.getModelAndView();
}
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
//步骤5 步骤6、解析视图并进行视图的渲染
//步骤5 由ViewResolver解析View(viewResolver.resolveViewName(viewName, locale))
//步骤6 视图在渲染时会把Model传入(view.render(mv.getModelInternal(), request, response);)
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
// 执行处理器相关的拦截器的完成后处理(HandlerInterceptor.afterCompletion)
//此处省略具体代码
catch (Exception ex) {
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
catch (Error err) {
ServletException ex = new NestedServletException("Handler processing failed", err);
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
finally {
// Clean up any resources used by a multipart request.
if (processedRequest != request) {
cleanupMultipart(processedRequest);
}
}
}
这个方法不长,基本就是负责其它方法的调用,从我们上面分析到前端请求第一步到达SpringMVC
后调用HandlerMapping
(处理器映射器)返回执行链HandlerExecutionChain
:
![](https://i-blog.csdnimg.cn/blog_migrate/9593bb45d788d1ed98984263b8e75c5e.png)
我们debug启动项目,打个断点看看,这个HandlerExecutionChain
到底是个什么东西。
![](https://i-blog.csdnimg.cn/blog_migrate/b528cd719a08c46f02f1a0333b6d92ba.png)
我们可以看到,当断点执行到HandlerExecutionChain
后,查看HandlerExecutionChain中的handler其实就是我们自己的请求访问的Controller,比如上面的我们请求登陆操作,handler里面的信息就是我们自己的LoginController。
同时包含LoginController的BeanType,前端要请求的方法,以及参数这个元数据信息,简单的概括就是:「HandlerExecutionChain里面handler就是我们要请求的Controller以及和一些interceptors信息」。
那么在获取到这个HandlerExecutionChain之前肯定是有初始化所有的Spring容器中的Bean以及所有的url与Bean对应的HandlerMapping对象。
这个都是在Spring中去完成的,这个我们后面在做了解,我们再进一步的了解HandlerMapping对象存储的内容,再getHandler方法里面进行打断点:
![](https://i-blog.csdnimg.cn/blog_migrate/9f7836610d73ca085d18000f11d99f61.png)
handlerMapping是一个List对象,里面主要是这七个成员信息,我们比较熟悉的就是BeanNameUrlMapping
和SimpleUrlHandlerMapping
对象,这些里面可以看出「handlerMapping主要存储的各种映射规则」,通过beanName或者url映射到对应的Bean对象。
![](https://i-blog.csdnimg.cn/blog_migrate/4afaa88a6aedf05a76eef68bd59253f4.png)
继续往里面看,可以看到这里有个applicationContext对象,这个也就我们的上下文,里面还有beanFactory,也就是Spring管理的Bean对象都在这个工厂里面,包括Spring自己的和我们自己定义Bean信息。
![](https://i-blog.csdnimg.cn/blog_migrate/29a5159dc79d3e75c7e61cb6b0f2848e.png)
这个就是HandlerMapping对象,主要「包含着的Bean映射规则、Bean详细信息。」
从HandlerMapping->HandlerExecutionChain的过程,用一句通俗易懂的话概括就是:「从茫茫的人海中找到了你(从beanFactory找到了请求对应的Controller以及方法)」。
当获取完我们的执行链后,接着就是获取我们的「处理器适配器」(HandlerAdapter
),
![](https://i-blog.csdnimg.cn/blog_migrate/ecbdf279435474b2904edd833fcbd7c7.png)
从getHandlerAdapter
的方法中可以看到,根据返回的handlerMapping
对象中的handler
对象来获取对应的HandlerAdapter
对象,直接返回。
![](https://i-blog.csdnimg.cn/blog_migrate/2bcef5b07c6e8c5a92095da46f5a20c8.png)
返回HandlerAdapter对象后,通过执行HandlerAdapter
的handle
方法获取ModelAndView对象,从这个方法的上面的注释来看:Actually invoke the handler.
。
实际就是通过「反射」的方式动态的执行我们自己的Controller中的方法,也就是前端请求的Controller,因为mappedHandler.getHandler()
返回的「handler对象包含着请求Controller的详细信息,包括全类名」。
![](https://i-blog.csdnimg.cn/blog_migrate/73fe69670e247b5a498d40dfdf191068.png)
获取到ModelAndView之后,接着就执行我们的拦截器的后置处理方法postHandle
。
![](https://i-blog.csdnimg.cn/blog_migrate/5fb14a83835cee536f4840b923b31b20.png)
从他的源码可以看出,它是获取到所有的拦截器,然后一个一个遍历,执行。
![](https://i-blog.csdnimg.cn/blog_migrate/eb561d4a6501fc8c72bb6b7ad1e30a50.png)
执行完所有拦截器的后置处理方法,就是最后䣌视图的渲染,这里执行的是processDispatchResult
方法,并把ModelAndView
对象作为参数传递进去。
![](https://i-blog.csdnimg.cn/blog_migrate/5c35e5f4bad3a14713331cb380b33d9c.png)
在processDispatchResult
方法里面最重要的就是render
方法了,执行视图的渲染,最后将渲染的结果呈现给用户。
![](https://i-blog.csdnimg.cn/blog_migrate/0a078fa98ca8dd8b002a7233b53ff75f.png)
到这里DispatcherServlet主要执行逻辑就讲完了,其实主要讲的还是SpringMVC的从前端请求->后台->前端这样的一个过程,限于篇幅,从源码的角度大概讲解这个的过程是怎么跑起来的。
一篇文章要把SpringMVC的都讲清楚是不可能的,SpringMVC所有讲下来,都能写一本书了,后续的源码我们继续精进,这篇作为一个大体脉络的了解。
特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:
长按订阅更多精彩▼
如有收获,点个在看,诚挚感谢