SpringMVC执行流程还不清楚?
MVC总结
1. 概述
还是之前的三个套路
1.1 是什么?
Spring
提供一套视图层的处理框架,他基于Servlet
实现,可以通过XML
或者注解进行我们需要的配置。
他提供了拦截器,文件上传,CORS
等服务。
1.2 为什么用?
原生Servlet
在大型项目中需要进过多重封装,来避免代码冗余,其次由于不同接口需要的参数不同,我们需要自己在Servlet
层 封装我们需要的参数,这对于开发者来说是一种重复且枯燥的工作,于是出现了视图层框架,为我们进行参数封装等功能。让开发者的注意力全部放在逻辑架构中,不需要考虑参数封装等问题。
1.3 怎么用
再聊怎么用之前,我们需要了解一下MVC
的工作原理。
他基于一个DispatcherServlet
类实现对各种请求的转发,即前端的所有请求都会来到这个Servlet中,然后这个类进行参数封装和请求转发,执行具体的逻辑。(第二章我们细聊)
1.3.1 XML
- 根据上面的原理,我们需要一个
DispatcherServlet
来为我们提供基础的Servlet
服务,我们可以通过servlet
规范的web.xml
文件,对该类进行初始化。并且声明该类处理所有的请求,然后通过这个类实现请求转发。 - 另外,我们还需要一个配置文件,用来配置我们需要的相关的
mvc
信息。
下面来看一个完整的web.xml
配置
<web-app> <servlet> <servlet-name>dispatchServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatchServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
1.3.2 注解
注解方式也是现在主流,SpringBoot
基于JavaConfig
实现了自动配置
实现方式:
在Servlet3.0
的时候定义了一个规范SPI
规范。
SPI
,全称为 Service Provider Interface
,是一种服务发现机制。它通过在ClassPath
路径下的META-INF/services
文件夹查找文件,自动加载文件里所定义的类。也就是在服务启动的时候会Servlet会自动加载该文件定义的类
我们看一眼这个文件里的内容。他内部定义了SpringServletContainerInitializer
容器初始化类,也就是说在Servlet
启动的时候会自动初始化这个类,这个类也是注解实现的关键。
这个类中存在一个onStartup
方法,这个也是当容器初始化的时候调用的方法,这个方法有两参数
Set<class<?>> webAppInitializerClasses
他代表了当前我们的Spring
容器中存在的web
初始化类。我们自己可以通过实现WebApplicationInitializer
类来自定义Servlet
初始化的时候执行的方法。ServletContext servletContex
代表了Servlet
上下文对象
org.springframework.web.SpringServletContainerInitializer
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { //启动逻辑 } }
具体看一下注解配置方式:
public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletCxt) { // Load Spring web application configuration AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); //一个配置类,@Configuration ac.register(AppConfig.class); //spring的那个refresh方法 ac.refresh(); // Create and register the DispatcherServlet DispatcherServlet servlet = new DispatcherServlet(ac); ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet); registration.setLoadOnStartup(1); registration.addMapping("/app/*"); } }
通过实现WebApplicationInitializer
接口,来作为MVC
的配置类,在加载SpringServletContainerInitializer
的时候加载这个类。
不过在具体的实现中,Spring
不建议我们这样做,他建议将Spring
和SpringMvc
分开,看个图
他在Spring之上加了一层Web环境配置。相当于在Spring的外面包装了一层Servlet
看一下此时的代码
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { //Spring配置文件 @Override protected Class<!--?-->[] getRootConfigClasses() { return new Class<!--?-->[] { RootConfig.class }; } //SpringMVC的配置文件 @Override protected Class<!--?-->[] getServletConfigClasses() { return new Class<!--?-->[] { App1Config.class }; } //指定DispatcherServlet可以拦截的路径 @Override protected String[] getServletMappings() { return new String[] { "/app1/*" }; } }
通过AbstractAnnotationConfigDispatcherServletInitializer
可以看到他实现了WebApplicationInitializer
接口,即在Servlet
初始化的时候会加载这个类。
AbstractContextLoaderInitializer
类,他初始化了Spring
AbstractDispatcherServletInitializer
类,初始化了DispatcherServlet
AbstractAnnotationConfigDispatcherServletInitializer
类,将两个类整合到一起
2. 实现原理
聊这个原理之前,先来聊聊他要干什么?
需求:请求分发;参数封装;结果返回
那如果我们自己来实现,该怎么办?(单说注解,先来看看我们怎么使用MVC
的)
- 一个
@Controller
注解,标识当前类为控制层接口, - 一个
RequestMapping
标识这个方法的URI
和请求方式等信息 - 一个
@ResponseBody
标识这个方法的返回类型为JSON
- 一个
test01
标识这个方法用来处理/test
请求
@Controller public class UserController { @GetMapping("/test") @ResponseBody public String test01(){ return "success" ; } }
接下来,我们通过我们已有的东西,看一下我们自己去处理请求的逻辑
先来想一下我们的请求过程:
- 前端发送一个
Http
请求,通过不同的uri
实现不同逻辑的处理 - 而这个
uri
和我们后端的定义的@RequestMapping
中的value
值相同 - 即我们可以通过一个
Map
结构,将value
作为key
,将method
的Class
对象作为一个value
存到一个MappingRegister
中 - 请求来了以后,通过
URI
从这个Map
中获取相应的Method
执行,如果没有对应的Method
给一个404
.
2.1 Spring加载
在上面的怎么用中提到了,他通过AbstractContextLoaderInitializer
来加载Spring
配置文件的。
此时关于Spring的东西已经加载好了,但并未进行初始化
2.2 MVC加载
同样也是通过AbstractDispatcherServletInitializer
类实现
2.2.1 DispatcherServlet
接下来我们具体看一下在这个期间,DispatcherServlet
如何处理请求的
作用:分发所有的请求
类继承结构图
可以看到他继承了HttpServlet
类,属于一个Servlet
,而在之前我们配置了这个Servlet
的拦截路径。他会将所有的请求拦截,然后做一个分发。
下面这个图各位看官应该非常熟悉:
其实DispatcherServlet
处理所有请求的方式在这个图里完全都体现了。
接下来聊一下他的设计思路吧。
当一个请求来的时候,进入doDispatch
方法中,然后处理这个请求,也是返回一个执行链
Spring
提供了三种方式的处理器映射器来处理不同的请求。
BeanNameUrlHandlerMapping
处理单独Bean
的请求。适用于实现Controller
和HttpRequestHandler
接口的类
@Component("/test02") public class HttpController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("HttpController执行"); return null; } }
@Component("/test01") public class HandlerController implements HttpRequestHandler { @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("handlerRequest"); } }
RequestMappingHandlerMapping
适用于方法类型的处理器映射。
@Controller public class UserController { @GetMapping("/test") public String test01(){ System.out.println("执行了"); return "success" ; } }
RouterFunctionMapping
,MVC
提供的一个处理通过函数式编程定义控制器的一个映射器处理器。需要直接添加到容器中,然后 通过路由一个地址,返回对应的数据
@Configuration @ComponentScan("com.bywlstudio.controller") @EnableWebMvc public class MvcConfig implements WebMvcConfigurer { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.jsp("/WEB-INF/pages/",".jsp"); } @Bean public RouterFunction<!--?--> routerFunctionA() { return RouterFunctions.route() .GET("/person/{id}", request1 -> ServerResponse.ok().body("Hello World")) .build(); } }
聊完了处理器映射器,再来聊一下处理器适配器
不同的请求方式,需要不同的处理方式,这也是Spring
为什么要提供一个适配器的原因。
RequestMappingHandlerAdapter
用来处理所有的方法请求,即通过@Controller
注解定义的HandlerFunctionAdapter
用来处理函数式的映射,即通过RouterFunctionMapping
定义的HttpRequestHandlerAdapter
用来处理实现了HttpRequestHandler
接口的SimpleControllerHandlerAdapter
用来处理实现了Controller
接口的请求
通过处理器适配器拿到适合的处理器,来处理对应的请求。
在处理器执行具体的请求的过程,实际上就是调用我们的方法的过程,于是就会出现返回值
通常对于返回值我们有两种方法:
@ResponseBody
直接返回JSON
数据。- 或者返回一个视图,该视图会被视图解析器解析。
对于返回值解析,MVC
提供了一个接口用于处理所有的返回值,这里我们仅仅谈上面的两种
ModelAndViewMethodReturnValueHandler
用于处理返回视图模型的请求RequestResponseBodyMethodProcessor
用于处理返回JSON
在我们拿到方法返回值以后,会调用this.returnValueHandlers.handleReturnValue
返回值解析器的这个方法,用于对视图模型的返回和JSON
数据的回显(直接回显到网页,此时返回的视图对象为null)
对于视图对象,通过视图解析器直接解析,进行数据模型渲染,然后回显给前端。
2.2.2 MappingRegistry
这个类存放了method
的映射信息。
class MappingRegistry { private final Map<t, mappingregistration<t>> registry = new HashMap<>(); private final Map<t, handlermethod> mappingLookup = new LinkedHashMap<>(); private final MultiValueMap<string, t> urlLookup = new LinkedMultiValueMap<>(); private final Map<string, list<handlermethod>> nameLookup = new ConcurrentHashMap<>(); private final Map<handlermethod, corsconfiguration> corsLookup = new ConcurrentHashMap<>(); private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
MVC
会从这个类中获取方法和URL
的引用。相当于Spring MVC
的容器。
3. 面试题
3.1 什么是MVC?什么是MVVM?
答:MVC
是一个架构模式,它有三个核心
- 视图(View)。用户界面
- 模型(Model)。业务数据
- 控制器(Controller)。接收用户输入,控制模型和视图进行数据交互
MVVM
也是一种架构模式,它也是三个核心
- 模型(
Model
)。后端数据 - 视图模型(
ViewModel
)。它完成了数据和视图的绑定 - 视图(
View
)。用户界面
它的核心思想是:通过ViewModel
将数据和视图绑定,用数据操作视图,常见框架为Vue
3.2 Spring Mvc执行流程
- 用户发送请求至
DispatcherServlet
DispatcherServelt
收到请求以后调用HandlerMapping
,找到请求处理器映射器(三选一)- 通过处理器映射器对应
URI
的处理器执行链(包含了拦截器,和处理器对象) - 调用处理器适配器,找到可以处理该执行链的处理器(四选一)
- 处理器具体执行,返回
ModelAndView
对象- 如果存在
@ResponseBody
注解,直接进行数据回显
- 如果存在
- 将返回的
ModelAndView
对象传给ViewResove
视图解析器解析,返回视图 DispatcherServlet
对View
进行渲染视图- 响应用户
>更多原创文章请关注公众号@MakerStack。转载请联系作者授权</handlermethod,></string,></string,></t,></t,></class<?></class<?>

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
接口的幂等性的多重考虑,你会了吗?
目录 [TOC] 前言 今天的主题:接口幂等性的解决方案。本来是想把对象的存储过程和内存布局肝出来的,但是临时产生了变化,哈哈,这部分内容我们留在下一期吧,有句话说的好,好事多磨,对吧。</br> 在实际项目开发中接口是我们在开发中经常接触到的,而且是经常经常要写,每一个项目可能都会伴随着大量的接口开发,在moon来涂鸦的这几个月,基本上就是在与接口作斗争了,新需求除了业务相关就是设计表和接口编写了。</br> 当然,在接口设计中我们要考虑很多问题,安全性,格式,设计等等,今天我们先来聊聊,在高并发环境下,接口幂等性的解决方案有哪些。 正文 1 接口幂等性 就是说在多次相同的操作下保证最终的结果是一致的。 其实这个概念还是比较简单的,很容易理解,那我们思考一个问题,如果不保证接口幂等性会有什么问题? 1.1 案例 我们简单的举个例子,现在有一个接口,提供了转账的功能,a要给b转账1000元,正常情况下我们接口一次性就调用成功了,但是却因为网络抖动等其它原因没有成功,于是就开始不停的重试,突然网络好了,但是这时却连续发出去了三个请求,但是这个接口没有保证幂等性,...
- 下一篇
干货笔记,数据仓库工具箱
《数据仓库工具箱—维度建模的完全指南》是数据仓库建模方面的经典著作, 1996年第一版出版被认为是数据仓库方面具有里程碑意义的事件。作者kimballl是数据仓库方面的权威,他将多年的数据仓库建模实战经验、技巧融入本书。他提出的许多维度建模概念被广泛应用于数据仓库的设计和开发中。 以下笔记包含四个部分组织: 一、数据仓库体系结构和建模过程、技巧。 二、维度表建模技术。 三、事实表建模技术。 四、行业建模经验。 一、数据仓库体系结构和建模过程、技巧 关键点:数据仓库体系结构、维度建模的四个步骤、数据仓库总线结构、一致性维度。 1、对于数据仓库来说,业务需求是第一位的。 2、数据仓库的目标: 随心所欲的访问数据。直观、明显、简单、易用、切割、合并、下钻、上卷。 一致的展现数据(相对于原来从多个系统中出来的报表不一致)。 适应性、扩展性、可维护性。 为领导决策提供支持。 3、数据仓库的组成。源数据-->数据准备区-->数据仓库(维度建模)-->数据聚集区(OLAP)-->展现。其中原系统到数据准备区属于ETL过程。数据仓库和数据聚集区本书称为数据展示。展现本书称为数据...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS关闭SELinux安全模块
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长