• 面试都要问的Spring MVC


    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请求
    @Controller
    public 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适用于方法类型的处理器映射。
    @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视图解析器解析,返回视图
    • DispatcherServletView进行渲染视图
    • 响应用户

    更多原创文章请关注公众号@MakerStack ,转载请联系作者授权

  • 相关阅读:
    LeeCode(两数相加)
    Linux vim中移动显示横线
    JAVA各版本的区别
    LNMP一键包安装完成后的目录结构
    tp6打开和关闭调试的方式
    windows安装Thinkphp6的过程
    Composer 的安装方法(一)
    解决:libsodium-1.0.17安装失败
    有些国内的安卓APP下载不了的解决办法
    Linux 安装时不能下载的问题处理办法
  • 原文地址:https://www.cnblogs.com/onlyzuo/p/13995683.html
Copyright © 2020-2023  润新知