探索SpringBoot中的SpringMVC

melon_jj發表於2018-11-01

spring boot就是一個大框架裡面包含了許許多多的東西,其中spring就是最核心的內容之一,當然就包含spring mvc。spring mvc 是隻是spring 處理web層請求的一個模組。因此他們的關係大概就是這樣:spring mvc < spring <springboot。


理清SpringBoot與SpringMVC的關係

Spring 框架就像一個家族,有眾多衍生產品例如 boot、security、jpa等等。但他們的基礎都是Spring 的 ioc和 aop ioc 提供了依賴注入的容器 aop ,解決了面向橫切面的程式設計,然後在此兩者的基礎上實現了其他延伸產品的高階功能。

Spring MVC是基於 Servlet 的一個 MVC 框架 主要解決 WEB 開發的問題,因為 Spring 的配置非常複雜,各種XML、 JavaConfig、hin處理起來比較繁瑣。

於是為了簡化開發者的使用,從而創造性地推出了Spring boot,約定優於配置,簡化了spring的配置流程。


說得更簡便一些:Spring 最初利用“工廠模式”(DI)和“代理模式”(AOP)解耦應用元件。

大家覺得挺好用,於是按照這種模式搞了一個 MVC框架(一些用Spring 解耦的元件),用開發 web 應用( SpringMVC )。

然後有發現每次開發都寫很多樣板程式碼,為了簡化工作流程,於是開發出了一些“懶人整合包”(starter),這套就是 Spring Boot。

Spring MVC的功能Spring MVC提供了一種輕度耦合的方式來開發web應用。Spring MVC是Spring的一個模組,式一個web框架。

通過Dispatcher Servlet, ModelAndView 和 View Resolver,開發web應用變得很容易。解決的問題領域是網站應用程式或者服務開發——URL路由、Session、模板引擎、靜態Web資源等等。


Spring Boot的功能Spring Boot實現了自動配置,降低了專案搭建的複雜度。

眾所周知Spring框架需要進行大量的配置,Spring Boot引入自動配置的概念,讓專案設定變得很容易。

Spring Boot本身並不提供Spring框架的核心特性以及擴充套件功能,只是用於快速、敏捷地開發新一代基於Spring框架的應用程式。也就是說,它並不是用來替代Spring的解決方案,而是和Spring框架緊密結合用於提升Spring開發者體驗的工具。

同時它整合了大量常用的第三方庫配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot應用中這些第三方庫幾乎可以零配置的開箱即用(out-of-the-box),大部分的Spring Boot應用都只需要非常少量的配置程式碼,開發者能夠更加專注於業務邏輯。Spring Boot只是承載者,輔助你簡化專案搭建過程的。如果承載的是WEB專案,使用Spring MVC作為MVC框架,那麼工作流程和你上面描述的是完全一樣的,因為這部分工作是Spring MVC做的而不是Spring Boot。對使用者來說,換用Spring Boot以後,專案初始化方法變了,配置檔案變了,另外就是不需要單獨安裝Tomcat這類容器伺服器了,maven打出jar包直接跑起來就是個網站,但你最核心的業務邏輯實現與業務流程實現沒有任何變化。

所以,用最簡練的語言概括就是:

Spring 是一個“引擎”;

Spring MVC 是基於Spring的一個 MVC 框架 ;

Spring Boot 是基於Spring4的條件註冊的一套快速開發整合包。


Spring MVC自動配置

Spring Boot為Spring MVC提供的auto-configuration適用於大多數應用,並在Spring預設功能上新增了以下特性:

  1. 引入ContentNegotiatingViewResolver和BeanNameViewResolver beans。

  2. 對靜態資源的支援,包括對WebJars的支援。

  3. 自動註冊Converter,GenericConverter,Formatter beans。

  4. 對HttpMessageConverters的支援。

  5. 自動註冊MessageCodeResolver。

  6. 對靜態index.html的支援。

  7. 對自定義Favicon的支援。

  8. 自動使用ConfigurableWebBindingInitializer bean。

如果保留Spring Boot MVC特性,你只需新增其他的MVC配置(攔截器,格式化處理器,檢視控制器等)。你可以新增自己的WebMvcConfigurerAdapter型別的@Configuration類,而不需要註解@EnableWebMvc。如果希望使用自定義的RequestMappingHandlerMapping,RequestMappingHandlerAdapter,或ExceptionHandlerExceptionResolver,你可以宣告一個WebMvcRegistrationsAdapter例項提供這些元件。

如果想全面控制Spring MVC,你可以新增自己的@Configuration,並使用@EnableWebMvc註解。

HttpMessageConverters

Spring MVC使用HttpMessageConverter介面轉換HTTP請求和響應,合適的預設配置可以開箱即用,例如物件自動轉換為JSON(使用Jackson庫)或XML(如果Jackson XML擴充套件可用,否則使用JAXB),字串預設使用UTF-8編碼。

可以使用Spring Boot的HttpMessageConverters類新增或自定義轉換類:

import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;

@Configuration
public class MyConfiguration {

    @Bean
    public HttpMessageConverters customConverters() {
        HttpMessageConverter<?> additional = ...
        HttpMessageConverter<?> another = ...
        return new HttpMessageConverters(additional, another);
    }
}複製程式碼

上下文中出現的所有HttpMessageConverter bean都將新增到converters列表,你可以通過這種方式覆蓋預設的轉換器列表(converters)。

自定義JSON序列化器和反序列化器

如果使用Jackson序列化,反序列化JSON資料,你可能想編寫自己的JsonSerializer和JsonDeserializer類。自定義序列化器(serializers)通常通過Module註冊到Jackson,但Spring Boot提供了@JsonComponent註解這一替代方式,它能輕鬆的將序列化器註冊為Spring Beans。

MessageCodesResolver

Spring MVC有一個實現策略,用於從繫結的errors產生用來渲染錯誤資訊的錯誤碼:MessageCodesResolver。Spring Boot會自動為你建立該實現,只要設定spring.mvc.message-codes-resolver.format屬性為PREFIX_ERROR_CODE或POSTFIX_ERROR_CODE(具體檢視DefaultMessageCodesResolver.Format列舉值)。

靜態內容

預設情況下,Spring Boot從classpath下的/static(/public,/resources或/META-INF/resources)資料夾,或從ServletContext根目錄提供靜態內容。這是通過Spring MVC的ResourceHttpRequestHandler實現的,你可以自定義WebMvcConfigurerAdapter並覆寫addResourceHandlers方法來改變該行為(載入靜態檔案)。

在單機web應用中,容器會啟動預設的servlet,並用它載入ServletContext根目錄下的內容以響應那些Spring不處理的請求。大多數情況下這都不會發生(除非你修改預設的MVC配置),因為Spring總能夠通過DispatcherServlet處理這些請求。

你可以設定spring.resources.staticLocations屬性自定義靜態資源的位置(配置一系列目錄位置代替預設的值),如果你這樣做,預設的歡迎頁面將從自定義位置載入,所以只要這些路徑中的任何地方有一個index.html,它都會成為應用的主頁。

此外,除了上述標準的靜態資源位置,有個例外情況是Webjars內容。任何在/webjars/**路徑下的資源都將從jar檔案中提供,只要它們以Webjars的格式打包。

注 如果你的應用將被打包成jar,那就不要使用src/main/webapp資料夾。儘管該資料夾是通常的標準格式,但它僅在打包成war的情況下起作用,在打包成jar時,多數構建工具都會預設忽略它。

Spring Boot也支援Spring MVC提供的高階資源處理特性,可用於清除快取的靜態資源或對WebJar使用版本無感知的URLs。

如果想使用針對WebJars版本無感知的URLs(version agnostic),只需要新增webjars-locator依賴,然後宣告你的Webjar。以jQuery為例,"/webjars/jquery/dist/jquery.min.js"實際為"/webjars/jquery/x.y.z/dist/jquery.min.js",x.y.z為Webjar的版本。

注 如果使用JBoss,你需要宣告webjars-locator-jboss-vfs依賴而不是webjars-locator,否則所有的Webjars將解析為404。

以下的配置為所有的靜態資源提供一種快取清除(cache busting)方案,實際上是將內容hash新增到URLs中,比如<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>:

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**複製程式碼

注 實現該功能的是ResourceUrlEncodingFilter,它在模板執行期會重寫資源連結,Thymeleaf,Velocity和FreeMarker會自動配置該filter,JSP需要手動配置。其他模板引擎還沒自動支援,不過你可以使用ResourceUrlProvider自定義模組巨集或幫助類。

當使用比如JavaScript模組載入器動態載入資源時,重新命名檔案是不行的,這也是提供其他策略並能結合使用的原因。下面是一個"fixed"策略,在URL中新增一個靜態version字串而不需要改變檔名:

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
spring.resources.chain.strategy.fixed.enabled=true
spring.resources.chain.strategy.fixed.paths=/js/lib/
spring.resources.chain.strategy.fixed.version=v12複製程式碼

使用以上策略,JavaScript模組載入器載入"/js/lib/"下的檔案時會使用一個固定的版本策略"/v12/js/lib/mymodule.js",其他資源仍舊使用內容hash的方式<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>。檢視ResourceProperties獲取更多支援的選項。

歡迎頁面

Spring Boot支援靜態和模板歡迎頁面。它首先index.html在配置的靜態內容位置中查詢 檔案。如果找不到,則會查詢index模板。如果找到任何一個,它將自動用作應用程式的歡迎頁面。、

自定義Favicon

Spring Boot favicon.ico在配置的靜態內容位置和類路徑的根目錄(按此順序)中查詢a 。如果存在這樣的檔案,它會自動用作應用程式的圖示。

路徑匹配和內容協商

Spring MVC可以通過檢視請求路徑並將它匹配到應用程式中定義的對映(例如@GetMapping Controller方法上的註釋),將傳入的HTTP請求對映到處理程式。

Spring Boot選擇預設禁用字尾模式匹配,這意味著請求"GET /projects/spring-boot.json"不會匹配 @GetMapping("/projects/spring-boot")對映。這被認為是Spring MVC應用程式的 最佳實踐。此功能在過去對於沒有傳送正確的“Accept”請求標頭的HTTP客戶端來說非常有用; 我們需要確保將正確的內容型別傳送到客戶端。如今,內容協商更可靠。

還有其他一些方法可以處理不一致地傳送適當的“接受”請求標頭的HTTP客戶端。我們可以使用查詢引數來確保類似的請求"GET /projects/spring-boot?format=json" 將對映到@GetMapping("/projects/spring-boot")以下內容,而不是使用字尾匹配:

spring.mvc.contentnegotiation.favor-parameter = true

#我們可以更改引數名稱,預設為“格式”:
#spring.mvc.contentnegotiation.parameter-name = myparam

#我們還可以通過以下方式註冊其他副檔名/媒體型別:
spring.mvc.contentnegotiation.media-types.markdown = text / markdown複製程式碼

如果您瞭解注意事項並仍然希望應用程式使用字尾模式匹配,則需要進行以下配置:

spring.mvc.contentnegotiation.favor-path-extension = true

#您也可以將該功能限制為已知擴充套件
#spring.mvc.pathmatch.use-registered-suffix-pattern = true

#我們還可以通過以下方式註冊其他副檔名/媒體型別:
#spring.mvc.contentnegotiation.media-types.adoc = text / asciidoc複製程式碼


ConfigurableWebBindingInitializer

Spring MVC使用WebBindingInitializer為每個特殊的請求初始化相應的WebDataBinder,如果你建立自己的ConfigurableWebBindingInitializer @Bean,Spring Boot會自動配置Spring MVC使用它。

模板引擎

正如REST web服務,你也可以使用Spring MVC提供動態HTML內容。Spring MVC支援各種各樣的模板技術,包括Velocity, FreeMarker和JSPs,很多其他的模板引擎也提供它們自己的Spring MVC整合。

Spring Boot為以下的模板引擎提供自動配置支援:

  1. FreeMarker

  2. Groovy

  3. Thymeleaf

  4. Velocity(1.4已不再支援)

  5. Mustache

注:由於在內嵌servlet容器中使用JSPs存在一些已知的限制,所以建議儘量不使用它們。

使用以上引擎中的任何一種,並採用預設配置,則模組會從src/main/resources/templates自動載入。

注:IntelliJ IDEA根據你執行應用的方式會對classpath進行不同的排序。在IDE裡通過main方法執行應用,跟從Maven,或Gradle,或打包好的jar中執行相比會導致不同的順序,這可能導致Spring Boot不能從classpath下成功地找到模板。如果遇到這個問題,你可以在IDE裡重新對classpath進行排序,將模組的類和資源放到第一位。或者,你可以配置模組的字首為classpath*:/templates/,這樣會查詢classpath下的所有模板目錄。

錯誤處理

Spring Boot預設提供一個/error對映用來以合適的方式處理所有的錯誤,並將它註冊為servlet容器中全域性的 錯誤頁面。對於機器客戶端(相對於瀏覽器而言,瀏覽器偏重於人的行為),它會產生一個具有詳細錯誤,HTTP狀態,異常資訊的JSON響應。對於瀏覽器客戶端,它會產生一個白色標籤樣式(whitelabel)的錯誤檢視,該檢視將以HTML格式顯示同樣的資料(可以新增一個解析為'error'的View來自定義它)。為了完全替換預設的行為,你可以實現ErrorController,並註冊一個該型別的bean定義,或簡單地新增一個ErrorAttributes型別的bean以使用現存的機制,只是替換顯示的內容。

注BasicErrorController可以作為自定義ErrorController的基類,如果你想新增對新context type的處理(預設處理text/html),這會很有幫助。你只需要繼承BasicErrorController,新增一個public方法,並註解帶有produces屬性的@RequestMapping,然後建立該新型別的bean。

你也可以定義一個@ControllerAdvice去自定義某個特殊controller或exception型別的JSON文件:

@ControllerAdvice(basePackageClasses = FooController.class)
public class FooControllerAdvice extends ResponseEntityExceptionHandler {

    @ExceptionHandler(YourException.class)
    @ResponseBody
    ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        return HttpStatus.valueOf(statusCode);
    }

}複製程式碼

在以上示例中,如果跟FooController相同package的某個controller丟擲YourException,一個CustomerErrorType型別的POJO的json展示將代替ErrorAttributes展示。

自定義錯誤頁面

如果想為某個給定的狀態碼展示一個自定義的HTML錯誤頁面,你需要將檔案新增到/error資料夾下。錯誤頁面既可以是靜態HTML(比如,任何靜態資原始檔夾下新增的),也可以是使用模板構建的,檔名必須是明確的狀態碼或一系列標籤。

例如,對映404到一個靜態HTML檔案,你的目錄結構可能如下:

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>複製程式碼

使用FreeMarker模板對映所有5xx錯誤,你需要如下的目錄結構:

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.ftl
             +- <other templates>複製程式碼

對於更復雜的對映,你可以新增實現ErrorViewResolver介面的beans:

public class MyErrorViewResolver implements ErrorViewResolver {

    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request,
            HttpStatus status, Map<String, Object> model) {
        // Use the request or status to optionally return a ModelAndView
        return ...
    }

}複製程式碼

你也可以使用Spring MVC特性,比如@ExceptionHandler方法和@ControllerAdvice,ErrorController將處理所有未處理的異常。

對映Spring MVC以外的錯誤頁面

對於不使用Spring MVC的應用,你可以通過ErrorPageRegistrar介面直接註冊ErrorPages。該抽象直接工作於底層內嵌servlet容器,即使你沒有Spring MVC的DispatcherServlet,它們仍舊可以工作。

@Bean
public ErrorPageRegistrar errorPageRegistrar(){
    return new MyErrorPageRegistrar();
}

// ...

private static class MyErrorPageRegistrar implements ErrorPageRegistrar {

    @Override
    public void registerErrorPages(ErrorPageRegistry registry) {
        registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
    }

}複製程式碼

注.如果你註冊一個ErrorPage,該頁面需要被一個Filter處理(在一些非Spring web框架中很常見,比如Jersey,Wicket),那麼該Filter需要明確註冊為一個ERROR分發器(dispatcher),例如:

@Bean
public FilterRegistrationBean myFilter() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(new MyFilter());
    ...
    registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
    return registration;
}複製程式碼

(預設的FilterRegistrationBean不包含ERROR dispatcher型別)。

WebSphere應用伺服器的錯誤處理

當部署到一個servlet容器時,Spring Boot通過它的錯誤頁面過濾器將帶有錯誤狀態的請求轉發到恰當的錯誤頁面。request只有在response還沒提交時才能轉發(forwarded)到正確的錯誤頁面,而WebSphere應用伺服器8.0及後續版本預設情況會在servlet方法成功執行後提交response,你需要設定com.ibm.ws.webcontainer.invokeFlushAfterService屬性為false來關閉該行為。

Spring HATEOAS

如果正在開發基於超媒體的RESTful API,你可能需要Spring HATEOAS,而Spring Boot會為其提供自動配置,這在大多數應用中都運作良好。 自動配置取代了@EnableHypermediaSupport,只需註冊一定數量的beans就能輕鬆構建基於超媒體的應用,這些beans包括LinkDiscoverers(客戶端支援),ObjectMapper(用於將響應編排為想要的形式)。ObjectMapper可以根據spring.jackson.*屬性或Jackson2ObjectMapperBuilder bean進行自定義。

通過註解@EnableHypermediaSupport,你可以控制Spring HATEOAS的配置,但這會禁用上述ObjectMapper的自定義功能。

CORS支援

跨域資源共享(CORS)是一個大多數瀏覽器都實現了的W3C標準,它允許你以靈活的方式指定跨域請求如何被授權,而不是採用那些不安全,效能低的方式,比如IFRAME或JSONP。

從4.2版本開始,Spring MVC對CORS提供開箱即用的支援。不用新增任何特殊配置,只需要在Spring Boot應用的controller方法上註解@CrossOrigin,並新增CORS配置。通過註冊一個自定義addCorsMappings(CorsRegistry)方法的WebMvcConfigurer bean可以指定全域性CORS配置:

@Configuration
public class MyConfiguration {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**");
            }
        };
    }
}複製程式碼
本文視訊:https://ke.qq.com/course/334772?tuin=431b3bb9


相關文章