关于mvc:SpringMVC执行流程还不清楚

51次阅读

共计 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不倡议咱们这样做,他倡议将 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, 转载请分割作者受权

正文完
 0