1. 程式人生 > >SpringMVC:處理一個http請求的完整過程

SpringMVC:處理一個http請求的完整過程

SpringMVC是一個基於DispatcherServlet的MVC框架,每一個請求最先訪問的都是DispatcherServlet,DispatcherServlet負責轉發每一個Request請求給相應的Handler,Handler處理以後再返回相應的檢視(View)和模型(Model),返回的檢視和模型都可以不指定,即可以只返回Model或只返回View或都不返回。
DispatcherServlet是繼承自HttpServlet的,既然SpringMVC是基於DispatcherServlet的,那麼我們先來配置一下DispatcherServlet,好讓它能夠管理我們希望它管理的內容。HttpServlet是在web.xml檔案中宣告的。

1.配置SpringMVC

使用 Maven 引入相關 jar 包

配置方式主要有兩種:通過web.xml檔案配置,或者通過程式碼進行配置;

使用web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app  version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"  
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xsi:schemaLocation
="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> <!-- 預設是/WEB-INF/applicationContext.xml -->
</context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/SpringMVC-servlet.xml</param-value> <!-- 預設是/WEB-INF/[servlet名字]-servlet.xml --> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>

spring-servlet.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"     
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"     
        xmlns:context="http://www.springframework.org/schema/context"     
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd   
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd   
       http://www.springframework.org/schema/context <a href="http://www.springframework.org/schema/context/spring-context-3.0.xsd">http://www.springframework.org/schema/context/spring-context-3.0.xsd</a>">

    <!-- 啟用spring mvc 註解 -->
    <context:annotation-config />

    <!-- 設定使用註解的類所在的jar包 -->
    <context:component-scan base-package="controller"></context:component-scan>

    <!-- 完成請求和註解POJO的對映 -->
    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
  
    <!-- 對轉向頁面的路徑解析。prefix:字首, suffix:字尾 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/jsp/" p:suffix=".jsp" />
</beans>

使用程式碼配置

建立一個 MyMvcConfig 類繼承 WebMvcConfigurerAdapter



@Configuration
@EnableWebMvc// 1
@EnableScheduling
@ComponentScan("com.wisely.highlight_springmvc4")
public class MyMvcConfig extends WebMvcConfigurerAdapter {// 2

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/classes/views/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setViewClass(JstlView.class);
        return viewResolver;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/assets/**").addResourceLocations(
                "classpath:/assets/");// 3

    }

    @Bean
    // 1
    public DemoInterceptor demoInterceptor() {
        return new DemoInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {// 2
        registry.addInterceptor(demoInterceptor());
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/index").setViewName("/index");
        registry.addViewController("/toUpload").setViewName("/upload");
        registry.addViewController("/converter").setViewName("/converter");
        registry.addViewController("/sse").setViewName("/sse");
        registry.addViewController("/async").setViewName("/async");
    }

     @Override
     public void configurePathMatch(PathMatchConfigurer configurer) {
     configurer.setUseSuffixPatternMatch(false);
     }

    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setMaxUploadSize(1000000);
        return multipartResolver;
    }


    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(converter());
    }

    @Bean 
    public MyMessageConverter converter(){
        return new MyMessageConverter();
    }



}

2.建立初始化類,實現 WebApplicationInitializer 介面

public class WebInitializer implements WebApplicationInitializer {//1

    @Override
    public void onStartup(ServletContext servletContext)
            throws ServletException {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(MyMvcConfig.class);
        ctx.setServletContext(servletContext); //2

        Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); //3
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
        servlet.setAsyncSupported(true);//1

    }

}

2.詳細瞭解SpringMVC處理一個請求的流程

Spring MVC工作流程圖
這裡寫圖片描述

這裡寫圖片描述

 1. 使用者向伺服器傳送請求,請求被Spring 前端控制Servelt DispatcherServlet捕獲;
  2. DispatcherServlet對請求URL進行解析,得到請求資源識別符號(URI)。然後根據該URI,呼叫HandlerMapping獲得該Handler配置的所有相關的物件(包括Handler物件以及Handler物件對應的攔截器),最後以HandlerExecutionChain物件的形式返回;
  3. DispatcherServlet 根據獲得的Handler,選擇一個合適的HandlerAdapter。(附註:如果成功獲得HandlerAdapter後,此時將開始執行攔截器的preHandler(...)方法)
   4.  提取Request中的模型資料,填充Handler入參,開始執行Handler(Controller)。 在填充Handler的入參過程中,根據你的配置,Spring將幫你做一些額外的工作:
  HttpMessageConveter: 將請求訊息(如Json、xml等資料)轉換成一個物件,將物件轉換為指定的響應資訊
  資料轉換:對請求訊息進行資料轉換。如String轉換成Integer、Double等
  資料根式化:對請求訊息進行資料格式化。 如將字串轉換成格式化數字或格式化日期等
  資料驗證: 驗證資料的有效性(長度、格式等),驗證結果儲存到BindingResult或Error中
  5.  Handler執行完成後,向DispatcherServlet 返回一個ModelAndView物件;
  6.  根據返回的ModelAndView,選擇一個適合的ViewResolver(必須是已經註冊到Spring容器中的ViewResolver)返回給DispatcherServlet ;
  7. ViewResolver 結合Model和View,來渲染檢視
  8. 將渲染結果返回給客戶端。

這裡寫圖片描述
1. 當DispatcherServlet接到請求時,他先回查詢適當的處理程式來處理請求。DispatcherServlet通過一個或者多個處理程式對映,將每個請求對映到處理程式中。處理程式對映配置在web應用程式的上下文中,是實現了HandlerMapping介面的Bean。它負責為請求返回一個適當的處理程式(也就是Controller)。處理程式對映通常根據請求的URL將請求對映到處理程式(Controller)。
2. 一旦DispatcherServlet選擇了適當的控制器,它就會呼叫這個控制器來處理請求。
3. 控制器處理完請求後,會將模型和檢視名(有時候是檢視物件)返回給DispatcherServlet。模型包含了控制器要傳遞給檢視進行顯示的屬性。如果返回的是檢視名稱,它會被解析成檢視物件再進行呈現。繫結模型和檢視的基本類是ModelAndView
4. 當DispatcherServlet接收到模型和檢視名稱時,它會將邏輯檢視名稱解析成檢視物件再進行呈現。DispatcherServlet從一個或者多個檢視解析器中解析檢視。檢視解析器配置在Web應用程式上下文中,是實現了ViewResolver介面的Bean。它的任務是根據邏輯檢視名稱返回試圖物件。
5. 一旦DispatcherServlet將檢視名稱解析稱為試圖物件,它就會呈現檢視物件,並傳遞控制器返回的模型。檢視的任務是將模型屬性展示給使用者。

DispatcherServlet接到請求後如何對映到Controller呢?
在Spring MVC中,Web請求被Web應用程式上下文中宣告的一個或者多個處理程式對映Bean(是吸納了HandlerMapping介面的Bean)對映到Controller。Spring MVC提供了幾種HandlerMapping實現:
1. BeanNameUrlHandlerMapping(預設情況),他根據Controller Bean名稱中指定的URL模式將請求對映到處理程式上。
eg. …
當你訪問http://**/welcome.htm這個URL時,DispatcherServlet通過BeanNameUrlHandlerMapping對映就找到了WelcomeController。
2. ControllerClassNameHandlerMapping,它是按控制器類名稱對映請求。
3. SimpleUrlHandlerMapping,用定製的對映定義來對映請求。

幾種常見的ViewResolver的解析:
1. InternalResourceViewResolver: 根據URL解析檢視。通過新增字首和字尾的方法,將每個檢視名稱都對映到一個URL上。
2. XmlViewResolver: 從XML配置檔案中解析檢視。將檢視宣告成Spring的Bean,並按他們的Bean Name進行解析。
3. ResourceBundleViewResolver: 從ResourceBundle中解析檢視。
4. 用多個檢視解析器解析檢視, 需要注意的是,您需要為你配置的檢視解析器配置解析的優先順序。 value越小,優先順序越高。