SpringBoot之響應式編程


一 Spring WebFlux Framework說明

Spring WebFlux 是 Spring Framework 5.0 中引入的新 reactive web framework。與 Spring MVC 不同,它不需要 Servlet API,完全異步和 non-blocking,並通過反應堆項目實現Reactive Streams規范。

Spring WebFlux 有兩種版本:功能和 annotation-based。 annotation-based 一個非常接近 Spring MVC model,如下面的示例所示:

@RestController
@RequestMapping("/users")
public class MyRestController {
​
    @GetMapping("/{user}")
    public Mono<User> getUser(@PathVariable Long user) {
        // ...
    }
​
    @GetMapping("/{user}/customers")
    public Flux<Customer> getUserCustomers(@PathVariable Long user) {
        // ...
    }
​
    @DeleteMapping("/{user}")
    public Mono<User> deleteUser(@PathVariable Long user) {
        // ...
    }
​
}

 

函數變量“WebFlux.fn”將路由配置與請求的實際處理分開,如下面的示例所示:

@Configuration
public class RoutingConfiguration {
​
    @Bean
    public RouterFunction<ServerResponse> monoRouterFunction(UserHandler userHandler) {
        return route(GET("/{user}").and(accept(APPLICATION_JSON)), userHandler::getUser)
                .andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers)
                .andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser);
    }
​
}
 
@Component
public class UserHandler {
​
    public Mono<ServerResponse> getUser(ServerRequest request) {
        // ...
    }
​
    public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
        // ...
    }
​
    public Mono<ServerResponse> deleteUser(ServerRequest request) {
        // ...
    }
}

 

 

WebFlux 是 Spring Framework 的一部分,其詳細信息可在reference 文檔中找到。

您可以根據需要定義盡可能多的RouterFunction beans 來模塊化 router 的定義。如果需要應用優先級,可以訂購 Beans。

要開始,請將spring-boot-starter-webflux模塊添加到 application。

在 application 中添加spring-boot-starter-webspring-boot-starter-webflux模塊會導致 Spring Boot auto-configuring Spring MVC,而不是 WebFlux。選擇此行為是因為許多 Spring 開發人員將spring-boot-starter-webflux添加到他們的 Spring MVC application 以使用 reactive WebClient。您仍然可以通過將所選的 application 類型設置為SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)來強制執行您的選擇。

Spring WebFlux Auto-configuration

Spring Boot 為 Spring WebFlux 提供 auto-configuration,適用於大多數 applications。

auto-configuration 在 Spring 的默認值之上添加以下 features:

如果你想保留 Spring Boot WebFlux features 並且想要添加額外的WebFlux configuration,你可以添加自己的@Configuration class 類型為WebFluxConfigurer而不是 @EnableWebFlux

如果要完全控制 Spring WebFlux,可以添加自己的@Configuration注釋@EnableWebFlux

帶有 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 編解碼器

Spring WebFlux 使用HttpMessageReaderHttpMessageWriter接口來轉換 HTTP 請求和響應。通過查看 classpath 中可用的 libraries,它們配置為CodecConfigurer以具有合理的默認值。

Spring Boot 通過使用CodecCustomizer實例進一步自定義。例如,spring.jackson.* configuration 鍵應用於 Jackson 編解碼器。

如果需要添加或自定義編解碼器,可以創建自定義CodecCustomizer component,如下面的示例所示:

import org.springframework.boot.web.codec.CodecCustomizer;
​
@Configuration
public class MyConfiguration {
​
    @Bean
    public CodecCustomizer myCodecCustomizer() {
        return codecConfigurer -> {
            // ...
        }
    }
​
}

 

你也可以利用Boot 的自定義 JSON 序列化器和反序列化器

靜態內容

默認情況下,Spring Boot 為 classpath 中名為/static(或/public/resources/META-INF/resources)的目錄提供靜態內容。它使用來自 Spring WebFlux 的ResourceWebHandler,以便您可以通過添加自己的WebFluxConfigurer並覆蓋addResourceHandlers方法來修改該行為。

默認情況下,資源映射到/**,但您可以通過設置spring.webflux.static-path-pattern property 來調整它。例如,將所有資源重新定位到/resources/**可以實現如下:

spring.webflux.static-path-pattern=/resources/**

您還可以使用spring.resources.static-locations自定義靜態資源位置。這樣做會將默認值替換為目錄位置列表。如果這樣做,默認的歡迎頁面檢測將切換到您的自定義位置。因此,如果您在啟動時的任何位置都有index.html,那么它就是 application 的主頁。

除了前面列出的“標准”靜態資源位置之外,還為Webjars 內容做了一個特例。如果 jar files 包含在 Webjars 格式中,則中包含路徑的所有資源都將從 jar files 提供。

Spring WebFlux applications 並不嚴格依賴於 Servlet API,因此不能將它們部署為 war files 並且不要使用src/main/webapp目錄。

模板引擎

與 REST web services 一樣,您也可以使用 Spring WebFlux 來提供動態 HTML 內容。 Spring WebFlux 支持各種模板技術,包括 Thymeleaf,FreeMarker 和 Mustache。

Spring Boot 包括對以下模板引擎的 auto-configuration 支持:

當您使用其中一個模板引擎和默認的 configuration 時,您的模板將從src/main/resources/templates自動獲取。

錯誤處理

Spring Boot 提供WebExceptionHandler,以合理的方式處理所有錯誤。它在處理 order 中的位置緊接在 WebFlux 提供的處理程序之前,這被認為是最后的。對於機器客戶端,它會生成一個 JSON 響應,其中包含錯誤,HTTP 狀態和 exception 消息的詳細信息。對於瀏覽器客戶端,有一個“whitelabel”錯誤處理程序,它以 HTML 格式呈現相同的數據。您還可以提供自己的 HTML 模板來顯示錯誤(請參閱下一節)。

自定義此 feature 的第一個步驟通常涉及使用現有機制,但替換或擴充錯誤內容。為此,您可以添加ErrorAttributes類型的 bean。

要更改錯誤處理行為,可以實現ErrorWebExceptionHandler並注冊該類型的 bean 定義。因為WebExceptionHandler非常 low-level,所以 Spring Boot 還提供了一個方便的AbstractErrorWebExceptionHandler來讓你以 WebFlux 的方式處理錯誤,如下面的例子所示:

public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
​
    // Define constructor here
​
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
​
        return RouterFunctions
                .route(aPredicate, aHandler)
                .andRoute(anotherPredicate, anotherHandler);
    }
​
}

 

要獲得更完整的圖片,您還可以直接子類化DefaultErrorWebExceptionHandler並覆蓋特定方法。

自定義錯誤頁面

如果要為給定狀態 code 顯示自定義 HTML 錯誤頁面,可以將文件添加到/error文件夾。錯誤頁面可以是靜態 HTML(即,添加到任何靜態資源文件夾下)或使用模板構建。文件的 name 應該是確切的狀態 code 或系列掩碼。

例如,要 map 404到靜態 HTML 文件,您的文件夾結構如下:

src/
+- main/
    +- java/
    |   + <source code>
    +- resources/
        +- public/
            +- error/
            |   +- 404.html
            +- <other public assets>

要使用 Mustache 模板 map 所有5xx錯誤,您的文件夾結構如下:

src/
+- main/
    +- java/
    |   + <source code>
    +- resources/
        +- templates/
            +- error/
            |   +- 5xx.mustache
            +- <other templates>

Web 過濾器

Spring WebFlux 提供了一個WebFilter接口,可以實現過濾 HTTP request-response 交換。在 application context 中找到的WebFilter beans 將自動用於過濾每個交換。

如果過濾器的 order 很重要,則可以實現Ordered或使用@Order進行注釋。 Spring Boot auto-configuration 可以為您配置 web 過濾器。執行此操作時,將使用以下 table 中顯示的訂單:

Web 過濾器 訂購
MetricsWebFilter Ordered.HIGHEST_PRECEDENCE + 1
WebFilterChainProxy(Spring Security) -100
HttpTraceWebFilter Ordered.LOWEST_PRECEDENCE - 10

二 WebClient

Spring Boot 將 auto-detect 用於驅動WebClient,具體取決於 application classpath 上可用的 libraries。目前,支持 Reactor Netty 和 Jetty RS client。

spring-boot-starter-webflux starter 默認依賴於io.projectreactor.netty:reactor-netty,它帶來了 server 和 client implementations。如果您選擇使用 Jetty 作為 reactive 服務器,則應該在 Jetty Reactive HTTP client library,org.eclipse.jetty:jetty-reactive-httpclient上添加依賴項。對服務器和 client 使用相同的技術具有優勢,因為它將自動在 client 和服務器之間共享 HTTP 資源。

開發人員可以通過提供自定義ReactorResourceFactoryJettyResourceFactory bean 來覆蓋 Jetty 和 Reactor Netty 的資源 configuration - 這將應用於 clients 和服務器。

如果您希望覆蓋 client 的該選項,您可以定義自己的ClientHttpConnector bean 並完全控制 client configuration。

您可以了解有關Spring Framework reference 文檔中的 WebClient configuration 選項的更多信息。

WebClient 自定義

WebClient自定義有三種主要方法,具體取決於您希望自定義應用的廣泛程度。

要使任何自定義的范圍盡可能窄,請 inject auto-configured WebClient.Builder然后根據需要調用其方法。 WebClient.Builder實例是有狀態的:構建器上的任何更改都會反映在隨后使用它創建的所有 client 中。如果要使用相同的構建器創建多個 client,還可以考慮使用WebClient.Builder other = builder.clone();克隆構建器。

要對所有WebClient.Builder實例進行 application-wide 添加自定義,可以聲明WebClientCustomizer beans 並在注入點本地更改WebClient.Builder

最后,您可以回退到原始 API 並使用WebClient.create()。在這種情況下,不應用 auto-configuration 或WebClientCustomizer

三 代碼演示

  • pom.xml

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-webflux</artifactId>
            </dependency><dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>io.projectreactor</groupId>
                <artifactId>reactor-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>

     

    注:webflux不能和web共存,webflux啟動的是netty。

  • 實體類

    public class User {
    ​
        private String id;
        
        private String name;
        
        
    ​
        public String getId() {
            return id;
        }
    ​
        public void setId(String id) {
            this.id = id;
        }
    ​
        public String getName() {
            return name;
        }
    ​
        public void setName(String name) {
            this.name = name;
        }
    ​
        public User(String id, String name) {
            super();
            this.id = id;
            this.name = name;
        }   
        
    }

     

  • Service層

    @Service
    public class UserService {
    ​
        
        private static final Map<String, User> dataMap = new HashMap<>();
        
        static{
            dataMap.put("1", new User("1", "小X老師"));
            dataMap.put("2", new User("2", "小D老師"));
            dataMap.put("3", new User("3", "小C老師"));
            dataMap.put("4", new User("4", "小L老師"));
            dataMap.put("5", new User("5", "小A老師"));
            dataMap.put("6", new User("6", "小S老師"));
            dataMap.put("7", new User("7", "小S老師"));
        }
        
        /**
         * 功能描述:返回用戶列表
         * @return
         */
        public Flux<User> list(){
            Collection<User> list = UserService.dataMap.values();
            
            return Flux.fromIterable(list);
        }
        
        
        /**
         * 功能描述:根據id查找用戶
         * @param id
         * @return
         */
        public Mono<User> getById(final String id){
            return Mono.justOrEmpty(UserService.dataMap.get(id));
        }
        
       /**
        * 功能描述:根據id刪除用戶
        * @param id
        * @return
        */
        public Mono<User> del(final String id){
            return Mono.justOrEmpty(UserService.dataMap.remove(id));
        }
        
        
        
    }

     

  • web層

    @RestController
    @RequestMapping("/api/v1/user")
    public class UserController {
        
    ​
        //@Autowired
        //private UserService userService;
        
        private final UserService userService;
        
         public UserController(final UserService userService) {
            this.userService = userService;
        }
        
        
        @GetMapping("/test")
        public Mono<String> test(){
            return Mono.just("hello");
        }
    ​
        
        /**
         * 功能描述:根據id找用戶
         * @param id
         * @return
         */
        @GetMapping("find")
        public Mono<User> findByid(final String id){
            return userService.getById(id);
        }
        
        
        /**
         * 功能描述:刪除用戶
         * @param id
         * @return
         */
        @GetMapping("del")
        public Mono<User> del(final String id){
            return userService.del(id);
        }
        
        /**
         * 功能描述:列表
         * @return
         */
        @GetMapping(value="list",produces=MediaType.APPLICATION_STREAM_JSON_VALUE)
        public Flux<User> list(){
            return userService.list().delayElements(Duration.ofSeconds(2));
        }
        
    }
    ​

     

  • 測試類

    //@RunWith(SpringRunner.class)
    //@SpringBootTest
    public class WebfluxApplicationTests {
    ​
    ​
        @Test
        public void testBase(){
            
            Mono<String> bodyMono = WebClient.create().get()
            .uri("http://localhost:8080/api/v1/user/find?id=1")
            .accept(MediaType.APPLICATION_JSON)
            .retrieve().bodyToMono(String.class);
            
            System.out.println(bodyMono.block());
            
        }
        
    ​
        @Test
        public void testBase2(){
            
            Mono<String> bodyMono = WebClient.create().get()
            .uri("http://localhost:8080/api/v1/user/find?id={id}",2)
            .accept(MediaType.APPLICATION_JSON)
            .retrieve().bodyToMono(String.class);
            
            System.out.println(bodyMono.block());
            
        }
    ​
    }
    注:着重講一下這個list方法,這個方法里面延遲2秒,會體現出是流一樣,每隔2秒出現一條數據。
    
    {"id":"1","name":"小X老師"}
    {"id":"2","name":"小D老師"}
    {"id":"3","name":"小C老師"}
    {"id":"4","name":"小L老師"}
    {"id":"5","name":"小A老師"}
    {"id":"6","name":"小S老師"}
    {"id":"7","name":"小S老師"}

     


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM