SpringMVC執行流程還不清楚?

MVC總結

1. 概述

仍是以前的三個套路前端

1.1 是什麼?

Spring提供一套視圖層的處理框架,他基於Servlet實現,能夠經過XML或者註解進行咱們須要的配置。java

他提供了攔截器,文件上傳,CORS等服務。web

1.2 爲何用?

原生Servlet在大型項目中須要進過多重封裝,來避免代碼冗餘,其次因爲不一樣接口須要的參數不一樣,咱們須要本身在Servlet層 封裝咱們須要的參數,這對於開發者來講是一種重複且枯燥的工做,因而出現了視圖層框架,爲咱們進行參數封裝等功能。讓開發者的注意力所有放在邏輯架構中,不須要考慮參數封裝等問題。面試

1.3 怎麼用

再聊怎麼用以前,咱們須要瞭解一下MVC的工做原理。spring

他基於一個DispatcherServlet類實現對各類請求的轉發,即前端的全部請求都會來到這個Servlet中,而後這個類進行參數封裝和請求轉發,執行具體的邏輯。(第二章咱們細聊)shell

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規範。mvc

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,轉載請聯繫做者受權
相關文章
相關標籤/搜索