天天看點

飛5的Spring Boot2(18)- web開發(上)

飛5的Spring Boot2(18)- web開發(上)

spring MVC​

本文主要是講解使用Spring Boot開發web應用,關于使用Spring 5的基于響應式的程式設計,将在下一節給出介紹。

開發Web應用

Spring Boot非常适合開發web應用程式。你可以使用内嵌的Tomcat,Jetty或Undertow輕輕松松地建立一個HTTP伺服器。大多數的web應用都可以使用spring-boot-starter-web子產品進行快速搭建和運作。

Tomcat是Apache 軟體基金會(Apache Software Foundation)的Jakarta 項目中的一個核心項目,由Apache、Sun 和其他一些公司及個人共同開發而成。由于有了Sun 的參與和支援,最新的Servlet 和JSP 規範總是能在Tomcat 中得到展現,Tomcat 5支援最新的Servlet 2.4 和JSP 2.0 規範。因為Tomcat 技術先進、性能穩定,而且免費,因而深受Java 愛好者的喜愛并得到了部分軟體開發商的認可,成為目前比較流行的Web 應用伺服器。

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

Undertow 是紅帽公司(RedHat)的開源産品,是 WildFly8(JBoos) 預設的 Web 伺服器。

官網API給出一句話概述Undertow:

Undertow is a flexible performant web server written in java, providing both blocking and non-blocking API’s based on NIO.

譯文: Undertow是一個用java編寫的靈活的高性能Web伺服器,提供基于NIO的阻塞和非阻塞API。

Spring Web MVC架構

Spring Web MVC架構(通常簡稱為”Spring MVC”)是一個富“模型,視圖,控制器”web架構, 允許使用者建立特定的@Controller或@RestController beans來處理傳入的HTTP請求,通過@RequestMapping注解可以将控制器中的方法映射到相應的HTTP請求。

示例:

1@RestController
 2@RequestMapping(value="/users")
 3public class MyRestController {
 4    @RequestMapping(value="/{user}", method=RequestMethod.GET)
 5    public User getUser(@PathVariable Long user) {
 6        // ...
 7    }
 8    @RequestMapping(value="/{user}/customers", method=RequestMethod.GET)
 9    List<Customer> getUserCustomers(@PathVariable Long user) {
10        // ...
11    }
12    @RequestMapping(value="/{user}", method=RequestMethod.DELETE)
13    public User deleteUser(@PathVariable Long user) {
14        // ...
15    }
16}      

Spring MVC是Spring架構的核心部分。

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注解。

飛5的Spring Boot2(18)- web開發(上)

HttpMessageConverters

Spring MVC使用HttpMessageConverter接口轉換HTTP請求和響應,預設配置可以開箱即用,例如對象自動轉換為JSON(使用Jackson庫)或XML(如果Jackson XML擴充可用,或者使用JAXB),字元串預設使用UTF-8編碼。

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

1import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
 2import org.springframework.context.annotation.*;
 3import org.springframework.http.converter.*;
 4@Configuration
 5public class MyConfiguration {
 6    @Bean
 7    public HttpMessageConverters customConverters() {
 8        HttpMessageConverter<?> additional = ...
 9        HttpMessageConverter<?> another = ...
10        return new HttpMessageConverters(additional, another);
11    }
12}      

上下文中出現的所有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中,比如:

1spring.resources.chain.strategy.content.enabled=true
2spring.resources.chain.strategy.content.paths=/**      

注意

實作該功能的是ResourceUrlEncodingFilter,它在模闆運作期會重寫資源連結,Thymeleaf,Velocity和FreeMarker會自動配置該filter,JSP需要手動配置。其他模闆引擎還沒自動支援,不過你可以使用ResourceUrlProvider自定義子產品。

當使用比如JavaScript子產品加載器動态加載資源時,重命名檔案是不行的,這也是提供其他政策并能結合使用的原因。下面是一個”fixed”政策,在URL中添加一個靜态version字元串而不需要改變檔案名:

1spring.resources.chain.strategy.content.enabled=true
2spring.resources.chain.strategy.content.paths=/**
3spring.resources.chain.strategy.fixed.enabled=true
4spring.resources.chain.strategy.fixed.paths=/js/lib/
5spring.resources.chain.strategy.fixed.version=v12      

使用以上政策,JavaScript子產品加載器加載”/js/lib/”下的檔案時會使用一個固定的版本政策”/v12/js/lib/mymodule.js”,其他資源仍舊使用内容hash的方式。檢視ResourceProperties擷取更多支援的選項。

注意

該特性在一個專門的博文和Spring架構參考文檔中有透徹描述。

歡迎頁面

飛5的Spring Boot2(18)- web開發(上)

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”)以下内容,而不是使用字尾比對:

1spring.mvc.contentnegotiation.favor-parameter = true
2#我們可以更改參數名稱,預設為“格式”:
3#spring.mvc.contentnegotiation.parameter-name = myparam
4#我們還可以通過以下方式注冊其他檔案擴充名/媒體類型:
5spring.mvc.contentnegotiation.media-types.markdown = text / markdown      

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

1spring.mvc.contentnegotiation.favor-path-extension = true
2#您也可以将該功能限制為已知擴充
3#spring.mvc.pathmatch.use-registered-suffix-pattern = true
4#我們還可以通過以下方式注冊其他檔案擴充名/媒體類型:
5#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為以下的模闆引擎提供自動配置支援:

  • FreeMarker
  • Groovy
  • Thymeleaf
  • Velocity(1.4已不再支援)
  • 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文檔:

1@ControllerAdvice(basePackageClasses = FooController.class)
 2public class FooControllerAdvice extends ResponseEntityExceptionHandler {
 3    @ExceptionHandler(YourException.class)
 4    @ResponseBody
 5    ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
 6        HttpStatus status = getStatus(request);
 7        return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
 8    }
 9    private HttpStatus getStatus(HttpServletRequest request) {
10        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
11        if (statusCode == null) {
12            return HttpStatus.INTERNAL_SERVER_ERROR;
13        }
14        return HttpStatus.valueOf(statusCode);
15    }
16}      

在以上示例中,如果跟FooController相同package的某個controller抛出YourException,一個CustomerErrorType類型的POJO的json展示将代替ErrorAttributes展示。

自定義錯誤頁面

如果想為某個給定的狀态碼展示一個自定義的HTML錯誤頁面,你需要将檔案添加到/error檔案夾下。錯誤頁面既可以是靜态HTML(比如,任何靜态資源檔案夾下添加的),也可以是使用模闆建構的,檔案名必須是明确的狀态碼或一系列标簽。

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

1src/
2 +- main/
3     +- java/
4     |   + <source code>
5     +- resources/
6         +- public/
7             +- error/
8             |   +- 404.html
9             +- <other public assets>      

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

1src/
2 +- main/
3     +- java/
4     |   + <source code>
5     +- resources/
6         +- templates/
7             +- error/
8             |   +- 5xx.ftl
9             +- <other templates>      

對于更複雜的映射,你可以添加實作ErrorViewResolver接口的beans:

1public class MyErrorViewResolver implements ErrorViewResolver {
2    @Override
3    public ModelAndView resolveErrorView(HttpServletRequest request,
4            HttpStatus status, Map<String, Object> model) {
5        // Use the request or status to optionally return a ModelAndView
6        return ...
7    }
8}      

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

映射Spring MVC以外的錯誤頁面

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

1@Bean
 2public ErrorPageRegistrar errorPageRegistrar(){
 3    return new MyErrorPageRegistrar();
 4}
 5// ...
 6private static class MyErrorPageRegistrar implements ErrorPageRegistrar {
 7    @Override
 8    public void registerErrorPages(ErrorPageRegistry registry) {
 9        registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
10    }
11}      

注意

如果你注冊一個ErrorPage,該頁面需要被一個Filter處理(在一些非Spring web架構中很常見,比如Jersey,Wicket),那麼該Filter需要明确注冊為一個ERROR分發器(dispatcher),例如:
1@Bean
2public FilterRegistrationBean myFilter() {
3    FilterRegistrationBean registration = new FilterRegistrationBean();
4    registration.setFilter(new MyFilter());
5    ...
6    registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
7    return registration;
8}      

(預設的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。

1@Configuration
 2public class MyConfiguration {
 3    @Bean
 4    public WebMvcConfigurer corsConfigurer() {
 5        return new WebMvcConfigurerAdapter() {
 6            @Override
 7            public void addCorsMappings(CorsRegistry registry) {
 8                registry.addMapping("/api/**");
 9            }
10        };
11    }
12}      

總結

繼續閱讀