共计 7219 个字符,预计需要花费 19 分钟才能阅读完成。
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, 转载请分割作者受权