整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          10個知識點讓你讀懂Spring MVC容器

          隨著 Spring Boot 逐步全面覆蓋到我們的項目之中,我們已經基本忘卻當年經典的 Servlet + Spring MVC 的組合,那讓人熟悉的 web.xml 配置。而本文,我們想先拋開 Spring Boot 到一旁,回到從前,一起來看看 Servlet 是怎么和 Spring MVC 集成,怎么來初始化 Spring 容器的。

          • Spring MVC概述
          • 注解驅動的控制器
          • 處理方法的數據綁定
          • 視圖和視圖解析器
          • 本地化
          • 文件上傳
          • WebSocket
          • 靜態資源處理
          • 攔截器
          • 異常處理

          一、Spring MVC的概述

          MVC:Model + View + Controller(數據模型 + 視圖 + 控制器)

          三層架構

          三層架構:Presentation tier + Application tier + Data tier(展示層 + 應用層 + 數據訪問層)

          MVC和三層架構的關系

          MVC和三層架構的關系, MVC只存在三層架構的展示層。

          M實際是數據模型,是包含數據的對象。在Spring MVC里,有一個專門的類叫Model,用來和V之間的數據交互、傳值。

          V指的是視圖界面,包含JSP、freeMarker、Velocity、Thymeleaf、Tile等。

          C就是控制器(Spring MVC的注解@Controller的類)。

          三層架構是整個應用的的架構,是由Spring框架負責管理的,一般項目結構中都由Service層、Dao層,這兩個反饋在應用層和數據訪問層。

          Spring MVC框架圍繞DispatcherServlet這個核心展開,它負責截獲請求并將其分派給相應的處理器處理。Spring MVC框架包括注解驅動控制器、請求及響應的信息處理、視圖解析、本地化解析、上傳文件解析、異常處理以及表單標簽綁定等內容。

          體系結構

          Spring MVC是基于Model 2實現的技術框架。Spring MVC通過一個DispatcherServlet接收所有請求,并將具體工作委托給其他組件進行處理。

          Spring MVC體系結構

          1. 客戶端發出一個HTTP請求,Web應用服務器接收到這個請求,如果匹配DispatcherServlet的請求映射路徑(在web.xml中指定),Web容器將該請求轉交給DispatcherServlet處理。
          2. DispatcherServlet接收到這個請求后,將根據請求的信息(包括URL、HTTP方法、請求報文頭、請求參數、Cookie等)及HandlerMapping的配置找到處理請求的處理器(Handler)。可將HandlerMapping看成路由控制器,將Handler看成目標主機。值得注意的是:Spring MVC中并沒有定義一個Handler接口,實際上任何一個Object都可以成為請求處理器。
          3. 當DispatcherServlet根據HandlerMapping得到對應當前請求的Handler后,通過HandlerAdapter對Handler進行封裝,再以統一的適配器接口調用Handler。 HandlerAdapter是Spring MVC的框架級接口,顧名思義HandlerAdapter是一個適配器,它用統一的接口對各種Handler方法進行調用。
          4. 處理器完成業務邏輯的處理后將運回一個ModelAndView給DispatcherServlet,ModelAndView包含了視圖邏輯名和模型數據信息。
          5. ModelAndView中包含的是“邏輯視圖名”而非真正的視圖對象,DispatcherServlet借由ViewResolver完成邏輯視圖名到真實視圖對象的解析工作。
          6. 當得到真實的視圖對象View后,DispatcherServlet就使用這個View對象對ModelAndView中的模型數據進行視圖渲染。
          7. 最終客戶端得到的響應消息,可能是一個普通的HTML頁而,也可能是一個XML或JSON串, 甚至是一張圖片或一個PDF文檔等不同的媒體形式。

          配置DispatcherServlet

          可以在web.xml中配置一個Servlet,并通過指定其處理的URL。

          <web-app xmlns="http://java.sun.com/xml/ns/javaee"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                   http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
          
              <!-- (1)從類路徑下加載Spring配置文件-->
              <context-param>
                  <param-name>contextConfigLocation</param-name>
                  <param-value>classpath:application-context.xml</param-value>
              </context-param>
          
              <!-- (2)負責啟動 Spring 容器的監聽器,它將引用(1)處的上下文參數獲得Spring配置文件的地址 -->
              <listener>
                  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
              </listener>
          
              <!-- (3)配置DispatcherServlet -->
              <servlet>
                  <servlet-name>web</servlet-name>
                  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
                  <load-on-startup>1</load-on-startup>
              </servlet>
              
              <!-- (4)指定處理的URL路徑 -->
              <servlet-mapping>
                  <servlet-name>web</servlet-name>
                  <url-pattern>/</url-pattern>
              </servlet-mapping>
          
          </web-app>
          復制代碼
          1. 在(1)處,通過contextConfigLocation參數指定業務層Spring容器的配置文件(多個配置文件用 , 分割)。
          2. 在(2)處,ContextLoaderListener是一個ServletLoaderListener,它通過contextConfigLocation指定的Spring配置文件啟動業務層的Spring容器。
          3. 在(3)處,配置了名為web的DispatcherServlet,它默認加載/WEB-INF/web-servlet.xml(-servlet.xml)的Spring配置文件,啟動Web層的Spring容器。Web層容器將作為業務層容器的子容器,Web層容器可以訪問業務層容器的Bean,而業務層容器訪問不了Web層容器的Bean。
          4. 在(4)處,通過指定DispatcherServlet處理 /* 全部的HTTP請求。一個web.xml可以配置多個DispatcherServlet,通過其對應的配置,讓每個DispatcherServlet處理不同的請求。

          DispatcherServlet 的配置參數

          可以通過的屬性指定配置參數:

          1. namespace參數:DispatcherServlet對應的命名空間,默認是WEB-INF/-servlet.xml。在顯式配置該參數后,新的配置文件對應的路徑是WEB-INF/.xml,例如如果將namespace設置為sample,則對應的Spring配置文件為WEB-INFmple.xml。
          2. contextConfigLocation:如果DispatcherServlet上下文對應的Spring配置文件有多個,則可以使用該屬性按照Spring資源路徑的方式指定,如classpath:sample1.xml,classpath:sample2.xml。
          3. publishContext:默認為true。DispatcherServlet根據該屬性決定是否將WebApplicationContext發布到ServletContext的屬性列表中,方便調用者可借由ServletContext找到WebApplicationContext實例,對應的屬性名為DispatcherServlet#getServletContextAttributeName()的返回值。
          4. publishEvents:默認為true。當DispatcherServlet處理完一個請求后,是否需要向容器發布一個ServletRequestHandleEvent事件。

          Spring容器配置

          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:context="http://www.springframework.org/schema/context"
                 xmlns:mvc="http://www.springframework.org/schema/mvc"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans
                 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                 http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context-4.0.xsd
                 http://www.springframework.org/schema/mvc
                 http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
          
              <context:component-scan base-package="com.ankeetc.web"/>
              <!-- 會自動注冊RequestMappingHandlerMapping與RequestMappingHandlerAdapter兩個Bean,這是SpringMVC為@Controllers分發請求所必需的 -->
              <!-- 并提供了數據綁定支持、@NumberFormatannotation支持、 @DateTimeFormat支持、@Valid支持、讀寫XML的支持和讀寫JSON的支持等功能。 -->
              <mvc:annotation-driven />
          
          </beans>
          復制代碼

          基于編程的配置

          Spring 4.0已經全面支持Servlet 3.0,可以使用編程的方式配置Servlet容器。在Servlet 3.0環境中,容器會在類路徑中查找實現javax.servlet.ServletContainerInitializer接口的類,如果發現實現類,就會用它來配置Servlet容器。Spring提供了這個接口的實現,名為SpringServletContainerInitializer,這個類反過來又查找實現WebApplicationInitializer的類并將配置的任務交給它們來完成。Spring還提供了一個WebApplicationInitializer基礎實現類AbstractAnnotationConfigDispatcherServletInitializer,使得它在注冊DispatcherServlet時只需要簡單地指定它的Servlet映射即可。

          public class WebApplicationInitilalizer implements WebApplicationInitializer {
              @Override
              public void onStartup(ServletContext servletContext) throws ServletException {
                  ServletRegistration.Dynamic registration = servletContext.addServlet("web", new DispatcherServlet());
                  registration.setLoadOnStartup(1);
                  registration.addMapping("/");
              }
          }
          復制代碼

          DispatcherServlet的內部邏輯

          protected void initStrategies(ApplicationContext context) {
                  initMultipartResolver(context);
                  initLocaleResolver(context);
                  initThemeResolver(context);
                  initHandlerMappings(context);
                  initHandlerAdapters(context);
                  initHandlerExceptionResolvers(context);
                  initRequestToViewNameTranslator(context);
                  initViewResolvers(context);
                  initFlashMapManager(context);
              }
          復制代碼

          DispatcherServlet#initStrategies()方法將在WebApplicationContext初始化后執行,此時Spring上下文中的Bean已經初始化完畢,該方法通過反射查找并裝配Spring容器中用戶自定義的Bean,如果找不到就裝配默認的組件實例。

          默認組件

          在DispatcherServlet.properties配置文件里邊,指定了DispatcherServlet所使用的默認組件。如果用戶希望采用非默認的組件,只需在Spring配置文件中配置自定義的組件Bean即可。

          # 本地化解析器
          org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
          
          # 主題解析器
          org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
          
          # 處理器解析器
          org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
              org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
          
          # 處理器適配器
          org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
              org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
              org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
          
          # 異常處理器
          org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
              org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
              org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
          
          # 視圖名稱處理器
          org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
          
          # 視圖解析器
          org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
          
          org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
          復制代碼

          DispatcherServlet裝配各型組件的邏輯

          二、注解驅動的控制器

          @RequestMapping映射請求

          1. 在POJO類上標注@Controller,再通過 context:component-scan 掃描到該類,可以是POJO成為一個能處理HTTP請求的控制器。
          2. 在控制器的類定義和方法定義處都可以使用@RequestMapping映射對應的處理方法。
          3. @RequestMapping不但支持標準的URL,還支持Ant風格和{XXX}占位符的URL。
          4. @RequestMapping和value、method、params及headers分別表示請求路徑、請求方法、請求參數及報文頭的映射條件。

          獲取請求內容

          org.springframework.web.context.request
          

          使用HttpMessageConverter

          HttpMessageConverter接口可以將請求信息轉換為一個對象(類型為T),并將對象(類型為T)綁定到請求方法的參數中或輸出為響應信息。DispatcherServlet默認已經安裝了RequestMethodHandlerAdapter作為HandlerAdapter組件的實現類,HttpMessageConverter即由RequestMethodHandlerAdapter使用,將請求信息轉換為對象,或將對象轉換為響應信息。

          HttpMessageConverter的實現類

          Spring為HttpMessageConverter提供了眾多的實現類:

          實現類

          實現類

          默認的HttpMessageConverter

          RequestMappingHandlerAdapter已經默認裝配了以下的HttpMessageConverter:

          • StringHttpMessageConverter
          • ByteArrayHttpMessageConverter
          • SourceHttpMessageConverter
          • AllEncompassingFormHttpMessageConverter

          裝配其他類型的HttpMessageConverter

          如果需要裝配其他類型的HttpMessageConverter,可以在Spring的Web容器上下文中自行定義一個RequestMappingHandlerAdapter,注冊若干HttpMessageConverter。如果在Spring web容器中顯式定義了一個RequestMappingHandlerAdapter,則Spring MVC將使用它 覆蓋 默認的RequestMappingHandlerAdapter。

          使用HttpMessageConverter

          1. 可以使用@RequestBody、@ResponseBody對處理方法進行標注
          2. 可以使用HttpEntity、ResponseEntity作為處理方法的入參或返回值

          RestTemplate是Spring的模板類,可以使用該類調用Web服務端的服務,它支持Rest風格的URL。

          結論

          1. 當控制器處理方法使用到@RequestBody、@ResponseBody 或 HttpEntity、ResponseEntity 時,Spring MVC才會使用注冊的HttpMessageConvertor對請求、相應消息進行處理。
          2. 當控制器處理方法使用到@RequestBody、@ResponseBody 或 HttpEntity、ResponseEntity時,Spring 首先根據請求頭或響應的Accept屬性選擇匹配的 HttpMessageConverter,進而根據參數類型或泛型類型的過濾得到匹配的 HttpMessageConverter,若找不到可用的 HttpMessageConverter 將報錯。
          3. @RequestBody、@ResponseBody不需要成對出現。

          處理XML和JSON

          Spring MVC提供了幾個處理XML和JSON格式的請求、響應消息的HttpMessageConverter:

          • MarshallingHttpMessageConverter:處理XML
          • Jaxb2RootElementHttpMessageConverter:處理XML,底層使用JAXB
          • MappingJackson2HttpMessageConverter:處理JSON格式

          只要在Spring Web容器中為RequestMappingHandlerAdapter裝配好相應的HttpMessageConverter,并在交互中通過請求的Accept指定MIME類型,Spring MVC就可以是服務器端的處理方法和客戶端透明的通過XML或JSON格式進行通信。

          <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
                  <property name="messageConverters">
                      <list>
                          <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
                          <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/>
                          <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
                      </list>
                  </property>
              </bean>
          復制代碼

          使用@RestController

          @RestController已經標注了@ResponseBody和@Controller,可以直接在控制器上標注該注解,就不用在每個@RequestMapping方法上添加@ResponseBody了。

          AsyncRestTemplate

          Spring 4.0提供了AsyncRestTemplate用于以異步無阻塞的方式進行服務訪問。

          public class WebApplicationInitilalizer implements WebApplicationInitializer {
              @Override
              public void onStartup(ServletContext servletContext) throws ServletException {
                  ServletRegistration.Dynamic registration = servletContext.addServlet("web", new DispatcherServlet());
                  registration.setLoadOnStartup(1);
                  // 此處要設置為true
                  registration.setAsyncSupported(true);
                  registration.addMapping("/");
              }
          }
          
          @RestController
          public class AsyncController {
          
              @RequestMapping(value = "/async", method = RequestMethod.GET)
              public Callable<String> async() {
                  System.out.println("hello!");
                  return new Callable<String>() {
                      @Override
                      public String call() throws Exception {
                          TimeUnit.SECONDS.sleep(5);
                          return "ASYNC";
                      }
                  };
              }
          }
          public class Main {
              public static void main(String[] args) {
                  AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
          
                  ListenableFuture<ResponseEntity<String>> future = asyncRestTemplate.getForEntity("http://localhost:8080/async", String.class);
          
                  System.out.println("return");
                  future.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
                      @Override
                      public void onFailure(Throwable ex) {
                          System.out.println("Failure");
                      }
          
                      @Override
                      public void onSuccess(ResponseEntity<String> result) {
                          System.out.println("Success");
                      }
                  });
              }
          }
          復制代碼

          處理模型數據

          Spring MVC提供了多種途徑輸出模型數據:

          1. ModelAndView:當處理方法返回值類型為ModelAndView時,方法體即可通過該對象添加模型數據;
          2. @ModelAttribute:在方法入參標注該注解后,入參的對象就會放到數據模型中;
          3. Map和Model:如果方法入參為org.framework.ui.Model、org.framework.ui.ModelMap、java.util.Map,當處理方法返回時,Map中的數據會自動添加到模型中;
          4. @SessionAttributes:將模型中的某個屬性暫存到HttpSession中,以便多個請求之間可以共享這個屬性。

          三、處理方法的數據綁定

          Spring會根據請求方法簽名的不同,將請求中的信息以一定方式轉換并綁定到請求方法的入參中,還會進行數據轉換、數據格式化及數據校驗等。

          數據綁定流程

          數據綁定

          Spring MVC通過反射對目標簽名進行分析,將請求消息綁定到處理方法的入參中。數據綁定的核心部件是DataBinder。Spring MVC主框架將ServletRequest對象及處理方法的入參對象實例傳遞給 DataBinder,DataBinder 首先調用裝配在 Spring Web 上下文中的 ConversionService 組件進行數據類型轉換、數據格式化等工作,將ServletRequest中的消息填充到入參對象中, 然后調用Validator組件對已經綁定了請求消息數據的入參對象進行數據合法性校驗,最 終生成數據綁定結果BindingResult對象。BindingResult包含了已完成數據綁定的入參 對象,還包含相應的校驗錯誤對象。Spring MVC抽取BindingResult中的入參對象及校驗錯誤對象,將它們賦給處理方法的相應入參。

          數據轉換

          類型轉換模塊位于org.framework.core.convert包中,同時由于歷史原因,Spring還支持JDK的PropertyEditor。

          ConversionService簡介

          ConversionService 是 Spring 類型轉換體系的核心接口,它定義了以下4個方法:

          • boolean canConvert(Class sourceType, Class targetType):判斷是否可以將一個Java類轉換為另一個Java類。
          • Boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType):需轉換的類將以成員變量的方式出現在宿主類中。TypeDescriptor不但描述了需轉換類的信息,還描述了從宿主類的上下文信息,如成員變量上的注解,成員變量是否以數組、集合或Map的方式呈現等。類型轉換邏輯可以利用這些信息做出 各種靈活的控制。
          • T convert(Object source, Class targetType):將原類型對象轉換為目標類型對象。
          • Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType):
            將對象從原類型對象轉換為目標類型對象,此時往往會用到所在宿主類的上下 文信息。

          第一個和第三個接口方法類似于PmpertyEditor,它們不關注類型對象所在的上下文 信息,只簡單地完成兩個類型對象的轉換,唯一的區別在于這兩個方法支持任意兩個類型的轉換。而第二個和第四個接口方法會參考類型對象所在宿主類的上下文信息,并利用這些信息進行類型轉換。

          使用ConversionService

          可以利用 org.springframework.context.support.ConversionServiceFactoryBean 在 Spring 的 上下文中定義一個ConversionService。Spring將自動識別出上下文中的ConversionService, 并在Bean屬性配置及Spring MVC處理方法入參綁定等場合使用它進行數據轉換。該FactoryBean創建ConversionService內建了很多轉換器,可完成大多數Java類型的轉換工作。除了包括將String對象轉換為各種基礎類型的對象外,還包括String、 Number、Array、Collection、Map、Properties 及 Object 之間的轉換器。可通過ConversionServiceFactoryBean的converters屬性注冊自定義的類型轉換器:

          <bean class="org.springframework.context.support.ConversionServiceFactoryBean">
                  <property name="converters">
                      <list>
                          <bean class="com.ankeetc.MyConverter"/>
                      </list>
                  </property>
              </bean>
          復制代碼

          Spring支持的轉換器

          Spring 在 org.springframework.core.convert.converter 包中定義了3種類型的轉換器接口,實現任意一個轉換器接口都可以作為自定義轉換器注冊到ConversionServiceFactoryBean中。這3種類型的轉換器接口分別為:

          • Converter<S, T>:將S類型的對象轉換為T類型的對象
          • GenericConverter:根據源類對象及目標類對象所在的宿主類的上下文信息進行類型轉換工作。該類還有一個子接口ConditionalGenericConverter,它添加了一個接口方法根據源類型及目標類型所在宿主類的上下文信息決定是否要進行類型轉換。
          • ConverterFactory:

          ConversionServiceFactoryBean 的 converters 屬性可接受 Converter、ConverterFactory、 GenericConverter或ConditionalGenericConverter接口的實現類,并把這些轉換器的轉換邏輯統一封裝到一個 ConversionService 實例對象中(GenericConversionService)。Spring 在Bean屬性配置及Spring MVC請求消息綁定時將利用這個ConversionService實例完成類型轉換工作。

          在Spring中使用@lnitBinder 和 WebBindingInitializer裝配自定義編輯器

          Spring也支持JavaBeans的PropertyEditor。可以在控制器中使用@InitBinder添加自定義的編輯器,也可以通過 WebBindingInitializer 裝配在全局范圍內使用的編輯器。

          @InitBinder
              public void initBinder(WebDataBinder binder) {
                  binder.registerCustomEditor(User.class, new PropertyEditorSupport() {
                      @Override
                      public void setAsText(String text) throws IllegalArgumentException {
                          User user = new User();
                          user.setName(text);
          
                          this.setValue(user);
                      }
                  });
              }
          復制代碼

          如果希望在全局范圍內使用,則可實現WebBindingInitializer接口并在該實現類中注冊。

          1. 實現WebBindingInitializer接口并在initBinder接口方法中注冊了自定義的編輯器。
          2. 在 Spring 上下文中通過 RequestMappingHandlerAdapter 裝配自定義的Initializer。

          順序

          對于同一個類型對象來說,如果既在ConversionService中裝配了自定義轉換器,又通過WebBindinglnitializer裝配了自定義編輯器,同時還在控制器中通過@InitBinder裝 配了自定義編輯器,那么Spring MVC將按以下優先順序查找對應類型的編輯器:

          1. 查詢通過@InitBinder裝配的自定義編輯器。
          2. 查詢通過ConversionService裝配的自定義轉換器。
          3. 查詢通過WebBindingInitializer裝配的自定義編輯器。

          數據格式化

          Spring的轉換器并不提供輸入及輸出信息格式化的工作,一般需要轉換的源類型數據(一般是字符串)都是具有一定格式的,在不同的本地化環境中, 同一類型的數據還會相應地呈現不同的顯示格式。Spring引入了一個新的格式化框架,這個框架位于org.springframework.format類包中。

          最重要的 Formatter接口

          注解驅動格式化AnnotationFormatterFactory

          為了讓注解和格式化的屬性類型關聯起來,Spring在Formatter所在的包中還提供了一個 AnnotationFormatterFactory 接口。

          啟用注解驅動格式化功能

          對屬性對象的輸入/輸出進行格式化,從本質上講依然屬于“類型轉換”的范疇。 Spring就是基于對象轉換框架植入“格式化”功能的。Spring 在格式化模塊中定義了一個實現 ConversionService 接口的 FormattingConversionService實現類,該實現類擴展了 GenericConversionService,因此它既具有類型轉換功能,又具有格式化功能。

          FormattingConversionService 也擁有一個對應的 FormattingConversionServiceFactoryBean 工廠類,后者用于在Spring上下文中構造一個FormattingConversionService。通過這個工廠類,既可以注冊自定義的轉換器,還可以注冊自定義的注解驅動邏輯。由于 FormattingConversionServiceFactoryBean 在內部會自動注冊 NumberFormatAnnotationFormatterFactory 和 JodaDateTimeFormatAnnotationFormatterFactory,因此裝配了 FormattingConversionServiceFactoryBean 后,就可以在 Spring MVC 入參綁定及模型數據輸出時使用注解驅動的格式化功能。

          值得注意的是,

          mvc:annotation-driven/ 標簽內部默認創建的ConversionService實例就是一個 FormattingConversionServiceFactoryBean。

          數據校驗

          Spring擁有自己獨立的數據校驗框架,同時支持JSR-303標準的校驗框架。Spring 的DataBinder在進行數據綁定時,可同時調用校驗框架完成數據校驗工作。在Spring MVC中,則可直接通過注解驅動的方式進行數據校驗。

          LocalValidatorFactoryBean 既實現了 Spring 的 Validator 接口,又實現了 JSR-303 的 Validator 接口。只要在 Spring 容器中定義了一個 LocalValidatorFactoryBean,即可將其注入需要數據校驗的Bean中。值得注意的是,Spring本身沒有提供JSR-303的實現,所以必須將JSR-303的實現 者(如Hibernate Validator)的JAR文件放到類路徑下,Spring將自動加載并裝配好 JSR-303的實現者。

          mvc:annotation-driven/

          會默認裝配一個 LocalValidatorFactoryBean,通過在處理方法的入參上標注@Valid注解,即可讓Spring MVC在完成數據綁定后執行數據校驗工作。

          四、視圖和視圖解析器

          五、本地化

          Spring提供了以下4個本地化解析器。

          • AcceptHeaderLocaleResolver:根據 HTTP 報文頭的 Accept-Language 參數確定本 地化類型。如果沒有顯式定義本地化解析器,則Spring MVC默認采用 AcceptHeaderLocaleResolver。
          • CookieLocaleResolver:根據指定的Cookie值確定本地化類型。
          • SessionLocaleResolver:根據Session中特定的屬性值確定本地化類型。
          • LocaleChangeInterceptor:從請求參數中獲取本次請求對應的本地化類型。

          六、文件上傳

          Spring MVC為文件上傳提供了直接支持,這種支持是通過即插即用的MultipartResolver 實現的。Spring 使用 Jakarta Commons FileUpload 技術實現了一個 MultipartResolver 實現 類:CommonsMultipartResolver。

          在Spring MVC上下文中默認沒有裝配MultipartResolver,因此默認情況下不能 處理文件的上傳工作。如果想使用Spring的文件上傳功能,則需要先在上下文中配置 MultipartResolver。

          七、WebSocket

          八、靜態資源處理

          1. mvc:default-servlet-handler/ :在 smart-servlet.xml 中配置 mvc:default-servlet-handler/ 后,會在 Spring MVC 上下文中定義一個 org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它將充當一個檢查員的角色,對進入DispatcherServlet的URL進行篩查。如果發現是靜態資源的請求,就將該請求轉由Web應用服務器默認的Servlet處理;如果不是靜態資源 的請求,則由DispatcherServlet繼續處理。
          2. mvc:resources/ : mvc:default-servlet-handler/ 將靜態資源的處理經由Spring MVC框架交回Web應 用服務器。而 mvc:resources/ 更進一步,由SpringMVC框架自己處理靜態資源,并添 加一些有用的附加功能。

          九、攔截器

          當收到請求時,DispatcherServlet將請求交給處理器映射(HandlerMapping),讓它找出對應該請求的HandlerExecutionChain對象。在講解HandlerMapping之前,有必要 認識一下這個 HandlerExecutionChain 對象。

          HandlerExecutionChain

          HandlerExecutionChain負責處理請求并返回ModelAndView的處理執行鏈,它包含一個處理該請求的處理器 (Handler),同時包括若干個對該請求實施攔截的攔截器(HandlerInterceptor)。當 HandlerMapping 返回 HandlerExecutionChain 后,DispatcherServlet 將請求交給定義在 HandlerExecutionChain中的攔截器和處理器一并處理。

          位于處理器鏈末端的是一個 Handler,DispatcherServlet通過 Handler Adapter適配器對 Handler進行封裝,并按統一的適配器接口對 Handler處理方法進行調用。可以在 web-servlet.xml 中配置多個攔截器,每個攔截器都可以指定一個匹配的映射路徑,以限制攔截器的作用范圍。

          十、異常處理

          Spring MVC通過 HandlerExceptionResolver處理程序的異常,包括處理器映射、數據綁定及處理器執行時發生的異常。 HandlerExceptionResolver僅有一個接口方法:Modelandview resolveException(HttpServletRequest request HttpServletResponse response Object handler, Exception ex)。當發生異常時,Spring MVC將調用 resolveException方法,并轉到 ModelAndView 對應的視圖中,作為一個異常報告頁面反饋給用戶。

          實現類

          HandlerExceptionResolver擁有4個實現類

          1. DefaultHandlerExceptionResolver:默認裝配了該類,將對應異常轉換為錯誤碼
          2. SimpleMappingExceptionResolver:對所有異常進行統一處理
          3. AnnotationMethodHandlerExceptionResolver:默認注冊了該類,允許通過@ExceptionHandler注解指定處理特定的異常
          4. ResponseStatusExceptionResolver

          文章到這里就結束了

          小編這里總結一份springMVC的思維導圖,想了解的小伙伴可以看看呢

          喜歡小編分享的技術文章可以點贊關注哦!

          小編這邊整理了一些spring MVC的技術資料以及面試題

          原文鏈接: https://juejin.cn/post/6902238540466225159

          如果覺得本文對你有幫助,可以轉發關注支持一下

          我們的開發工程中經常會使用到各種圖,所謂的圖就是由節點和節點之間的連接所形成的系統,數學上專門有一個分支叫圖論(Graph Theroy)。利用圖我們可以做很多工具,比如思維導圖,流程圖,狀態機,組織架構圖,等等。今天我要做的是用開源的HTML5工具來快速構造一個做圖的工具。

          工具選擇

          工預善其事,必先利其器。第一件事是選擇一件合適的工具,開源時代,程序員還是很幸福的,選擇很多。

          • flowchart.js http://adrai.github.io/flowchart.js/ , 基于SVG創建Flow Chart
          • go.js http://www.gojs.net/latest/index.html go.js 提供一整套的JS工具 ,支持各種交互式圖表的創建。有免費版和收費版
          • joint.js http://www.jointjs.com/ joint.js 是另一個創建圖標庫的工具,也提供免費版和商業版
          • jsPlumb http://www.jsplumb.org/ jsPlumb是一套開源的流程圖創建工具 ,小巧精悍,使用簡單
          • d3 http://d3js.org 在html5領域,d3可謂是最好的可視化基礎庫,提供方面的DOM操作,非常強大。

          最終,我選擇了jsPlumb,因為它完全開源,使用很簡單,用D3的話可能會多花很多功夫。joint.js也不錯。大家可以根據自己的需要選擇。

          構建靜態應用

          下面我們一步一步的來使用jsPlumb來創建我們的流程圖工具。

          第一步是等待DOM和jsPlumb初始化完畢,類似document.ready()和jquery.ready(), 要使用jsPlumb, 需要把代碼放在這個函數里:

          jsPlumb.ready(function()?{
          ????//?...?your?code?goes?here?...
          }


          創建一個jsPlumb的實例,并初始化jsPlumb的配置參數:

          //Initialize?JsPlumb
          var?color?=?"#E8C870";
          var?instance?=?jsPlumb.getInstance({
          ????//?notice?the?'curviness'?argument?to?this?Bezier?curve.??the?curves?on?this?page?are?far?smoother
          ????//?than?the?curves?on?the?first?demo,?which?use?the?default?curviness?value.??????
          ????Connector?:?[?"Bezier",?{?curviness:50?}?],
          ????DragOptions?:?{?cursor:?"pointer",?zIndex:2000?},
          ????PaintStyle?:?{?strokeStyle:color,?lineWidth:2?},
          ????EndpointStyle?:?{?radius:5,?fillStyle:color?},
          ????HoverPaintStyle?:?{strokeStyle:"#7073EB"?},
          ????EndpointHoverStyle?:?{fillStyle:"#7073EB"?},
          ????Container:"container-id"
          ?});


          這里給給出了一些配置包括,連接線(這里配置了一個貝塞爾曲線),線的風格,連接點得風格。Container需要配置一個對應的DIV容器的id。(這里也可以使用setContainer的方法)

          下面我們要創建一個節點(node),每一個節點可以用一個DIV來實現。我這里提供了一個函數來創建節點。

          function?addNode(parentId,?nodeId,?nodeLable,?position)?{
          ??var?panel?=?d3.select("#"?+?parentId);
          ??panel.append('div').style('width','120px').style('height','50px')
          ????.style('position','absolute')
          ????.style('top',position.y).style('left',position.x)
          ????.style('border','2px?#9DFFCA?solid').attr('align','center')
          ????.attr('id',nodeId).classed('node',true)
          ????.text(nodeLable);
          
          ??return?jsPlumb.getSelector('#'?+?nodeId)[0];
          }


          這里做的事情就是創建了一個DIV元素,并放在對應的容器的制定位置上,注意為了支持拖拽的功能,必須使用position:absolute 。

          我使用D3來操作DOM,大家可能會更習慣JQuery,這純屬個人喜好的問題。

          最后返回創建節點的實例引用,這是的selector使用了jsPlumb.getSelector()方法,它和JQuery的selector是一樣的,這樣用的好處是你可以使用不同的DOM操作庫,例如Vanilla

          下面我使用一個函數來創建端點/錨點(anchor),錨點就是節點上的連接點,用于連接不同的節點。

          function?addPorts(instance,?node,?ports,?type)?{
          ??//Assume?horizental?layout
          ??var?number_of_ports?=?ports.length;
          ??var?i?=?0;
          ??var?height?=?$(node).height();??//Note,?jquery?does?not?include?border?for?height
          ??var?y_offset?=?1?/?(?number_of_ports?+?1);
          ??var?y?=?0;
          
          ??for?(?;?i?<?number_of_ports;?i++?)?{
          ????var?anchor?=?[0,0,0,0];
          ????var?paintStyle?=?{?radius:5,?fillStyle:'#FF8891'?};
          ????var?isSource?=?false,?isTarget?=?false;
          ????if?(?type?===?'output'?)?{
          ??????anchor[0]?=?1;
          ??????paintStyle.fillStyle?=?'#D4FFD6';
          ??????isSource?=?true;
          ????}?else?{
          ??????isTarget?=true;
          ????}
          
          ????anchor[1]?=?y?+?y_offset;
          ????y?=?anchor[1];
          
          ????instance.addEndpoint(node,?{
          ??????uuid:node.getAttribute("id")?+?"-"?+?ports[i],
          ??????paintStyle:?paintStyle,
          ??????anchor:anchor,
          ??????maxConnections:-1,
          ??????isSource:isSource,
          ??????isTarget:isTarget
          ????});
          ??}
          }


          instance是jsPlumb的實例

          node是我們用addNode方法創建的Node實例

          ports,是一個string的數組,指定端點的個數和名字

          type,可能是output或者input,指定端點的種類,一個節點的輸出端口可以連接另一個節點的輸入端口。

          這里anchor是一個四維數組,0維和1維分別是錨點在節點x軸和y軸的偏移百分比。我這里希望把端口畫在節點的左右兩側,并按照端口的數量均勻分布。

          最后使用instance.addEndpoint來創建端點。注意這里只要指定isSource和isTarget就可以用drag&drop的方式來連接端點,非常方便。

          下面一步我們提供一個函數來連接端點:

          function?connectPorts(instance,?node1,?port1,?node2?,?port2)?{
          ??//?declare?some?common?values:
          ??var?color?=?"gray";
          ??var?arrowCommon?=?{?foldback:0.8,?fillStyle:color,?width:5?},
          ??//?use?three-arg?spec?to?create?two?different?arrows?with?the?common?values:
          ??overlays?=?[
          ????[?"Arrow",?{?location:0.8?},?arrowCommon?],
          ????[?"Arrow",?{?location:0.2,?direction:-1?},?arrowCommon?]
          ??];
          
          ??var?uuid_source?=?node1.getAttribute("id")?+?"-"?+?port1;
          ??var?uuid_target?=?node2.getAttribute("id")?+?"-"?+?port2;
          
          ??instance.connect({uuids:[uuid_source,?uuid_target]});
          }


          node1和node2是源節點和目標節點的引用,port1和port2是源端口和目標端口的名字。

          使用instance.connect方法來創建連接。 overlays用來添加連接線的箭頭效果或者其他風格,我這里沒有使用,因為覺得都不是很好看。大家如果要用,只要把overlays加入到instance.connect的方法參數就可以了。

          調用以上方法來創建節點,端點和連接線。

          var?node1?=?addNode('container-id','node1',?'node1',?{x:'80px',y:'20px'});
          var?node2?=?addNode('container-id','node2',?'node2',?{x:'280px',y:'20px'});
          
          addPorts(instance,?node1,?['out1','out2'],'output');
          addPorts(instance,?node2,?['in','in1','in2'],'input');
          
          connectPorts(instance,?node1,?'out2',?node2,?'in');


          這里我們創建了兩個節點,第一個節點有兩個輸出端口,第二個節點有三個輸入端口,然后把第一個節點的out2端口連接到第二個端點的in端口。效果如下:

          最后我們給節點增加drag&drop的功能,這樣我們就可以拖動這些節點來改變圖的布局了。

          instance.draggable($('.node'));


          這里似乎依賴于JQuery-UI,我還不是很清楚。

          交互式創建節點

          我們已經初步具有了創建圖的功能,可是節點的創建必須通過程序,我們希望用交互的方式來創建節點。

          通常我們希望有一個tree view的控件,讓后通過拖拽來創建對應類型的節點。這里我使用了這個開源的tree view,基于bootstrap https://github.com/jonmiles/bootstrap-treeview

          我們先創建一個tree view:

          function?getTreeData()?{
          ??var?tree?=?[
          ????{
          ??????text:?"Nodes",
          ??????nodes:?[
          ????????{
          ??????????text:?"Node1",
          ????????},
          ????????{
          ??????????text:?"Node2"
          ????????}
          ??????]
          ????}
          ??];?
          
          ??return?tree;
          }
          //Initialize?Control?Tree?View
          $('#control-panel').treeview({data:?getTreeData()});


          樹上有兩個節點:

          然后我實現從樹上拖拽對應的節點,到流程圖上的邏輯。

          //Handle?drag?and?drop
          $('.list-group-item').attr('draggable','true').on('dragstart',?function(ev){
          ??//ev.dataTransfer.setData("text",?ev.target.id);
          ??ev.originalEvent.dataTransfer.setData('text',ev.target.textContent);
          ??console.log('drag?start');
          });
          
          $('#container-id').on('drop',?function(ev){
          ??//avoid?event?conlict?for?jsPlumb
          ??if?(ev.target.className.indexOf('_jsPlumb')?>=?0?)?{
          ????return;
          ??}
          
          ??ev.preventDefault();
          ??var?mx?=?''?+?ev.originalEvent.offsetX?+?'px';
          ??var?my?=?''?+?ev.originalEvent.offsetY?+?'px';
          
          ??console.log('on?drop?:?'?+?ev.originalEvent.dataTransfer.getData('text'));
          ??var?uid?=?new?Date().getTime();
          ??var?node?=?addNode('flow-panel','node'?+?uid,?'node',?{x:mx,y:my});
          ??addPorts(instance,?node,?['out'],'output');
          ??addPorts(instance,?node,?['in1','in2'],'input');
          ??instance.draggable($(node));
          }).on('dragover',?function(ev){
          ??ev.preventDefault();
          ??console.log('on?drag?over');
          });


          這里要注意的是要避免和jsPlumb拖拽端點的邏輯沖突,當檢測到target是jsPlumb對象是需要直接從drop方法中退出以執行對應的jsPlumb的drop邏輯。

          好了,一個繪制流程圖的軟件工具初步完工。

          我把代碼放在oschina的代碼托管服務上了, 大家有興趣可以去試試。

          ervlet容器主要是JavaWeb應用提供運行時環境,所以也可以稱之為JavaWeb應用容器,或者Servlet/JSP容器。Servlet容器主要負責管理Servlet、JSP的生命周期以及它們的共享數據。

          Servlet容器有哪些:

          目前最流行的Servlet容器軟件包括: Tomcat、Jetty、Jboss等。

          Tomcat

          Tomcat和IIS等Web服務器一樣,具有處理HTML頁面的功能,另外它還是一個Servlet和JSP容器,獨立的Servlet容器是Tomcat的默認模式。不過,Tomcat處理靜態HTML的能力不如Apache服務器。

          Jetty

          Jetty 是一個開源的servlet容器,它為基于Java的web容器,例如JSP和servlet提供運行環境。Jetty是使用Java語言編寫的,它的API以一組JAR包的形式發布。開發人員可以將Jetty容器實例化成一個對象,可以迅速為一些獨立運行(stand-alone)的Java應用提供網絡和web連接。

          Jboss

          Jboss是一個基于J2EE的開放源代碼的應用服務器。 JBoss代碼遵循LGPL許可,可以在任何商業應用中免費使用。JBoss是一個管理EJB的容器和服務器,支持EJB 1.1、EJB 2.0和EJB3的規范。但JBoss核心服務不包括支持servlet/JSP的WEB容器,一般與Tomcat或Jetty綁定使用。

          Servlet是和平臺無關的服務器端組件(java編寫的,跨平臺),它運行在Servlet容器中。

          Servlet容器負責Servlet和客戶的通信以及調用Servlet的方法,Servlet和客戶的通信采用“請求/響應”的模式 Servlet可完成如下功能:

          1、創建并返回基于客戶請求的動態HTML頁面

          2、創建可嵌入到現有HTML 頁面中的部分HTML 頁面(HTML 片段)

          3、與其它服務器資源(如數據庫或基于Java的應用程序)進行通信

          Servlet容器響應客戶請求過程:


          主站蜘蛛池模板: 亚洲色偷偷偷网站色偷一区| 亚洲国产情侣一区二区三区 | 亚洲视频在线观看一区| 国产伦精品一区二区三区精品 | 亚洲国产精品一区二区三区在线观看 | 视频一区视频二区制服丝袜| 亚洲AV日韩综合一区| 亚洲丶国产丶欧美一区二区三区| 久久久久久人妻一区精品| 国产一区二区三区夜色| 精品aⅴ一区二区三区| 精品亚洲一区二区| 人妻体内射精一区二区三区| 国产91大片精品一区在线观看| 精品人妻系列无码一区二区三区| 国产91久久精品一区二区| 国产一区二区三区乱码在线观看| 正在播放国产一区| 一级毛片完整版免费播放一区 | 另类国产精品一区二区| 国产女人乱人伦精品一区二区| 国产一区二区三区在线观看影院| 一区二区高清视频在线观看| 国产波霸爆乳一区二区| 亚洲一区在线视频观看| 99国产精品欧美一区二区三区| 精品视频在线观看一区二区| 大伊香蕉精品一区视频在线| 亚洲AV美女一区二区三区| 国产高清在线精品一区| 国产成人av一区二区三区不卡 | 亚洲一区二区观看播放| 国产一区在线视频观看| 国产在线视频一区| 精品黑人一区二区三区| 日本一区二区三区在线看| 日本大香伊一区二区三区| 精品三级AV无码一区| 国产视频福利一区| 亚洲日韩精品一区二区三区无码 | 欧美日韩精品一区二区在线观看|