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不倡议咱们这样做,他倡议将SpringSpringMvc离开,看个图

他在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申请
@Controllerpublic class UserController {    @GetMapping("/test")    @ResponseBody    public String test01(){        return "success" ;    }}

接下来,咱们通过咱们已有的货色,看一下咱们本人去解决申请的逻辑

先来想一下咱们的申请过程:

  • 前端发送一个Http申请,通过不同的uri实现不同逻辑的解决
  • 而这个uri和咱们后端的定义的@RequestMapping中的value值雷同
  • 即咱们能够通过一个Map构造,将value作为key,将methodClass对象作为一个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的申请。实用于实现ControllerHttpRequestHandler接口的类
@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实用于办法类型的处理器映射。
@Controllerpublic class UserController {    @GetMapping("/test")    public String test01(){        System.out.println("执行了");        return "success" ;    }}
  • RouterFunctionMapping,MVC提供的一个解决通过函数式编程定义控制器的一个映射器处理器。须要间接增加到容器中,而后 通过路由一个地址,返回对应的数据
@Configuration@ComponentScan("com.bywlstudio.controller")@EnableWebMvcpublic 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视图解析器解析,返回视图
  • DispatcherServletView进行渲染视图
  • 响应用户
更多原创文章请关注笔者公众号@MakerStack,转载请分割作者受权