場景 | 要求 | 編程方法 |
---|---|---|
電商和金融行業 | 數據一致性要求很是高 | 高併發的時候須要鎖或者其它機制來保證一些重要數據的一致性; 可是性能也降低的很快; |
遊戲,新聞,視頻,廣告 | 不須要很高的數據一致性 | 對併發數和響應速度要求比較高 |
這種場景下,出現了響應式編程。依賴的基礎技術點以下:
技術點 | 說明 |
---|---|
servlet3.1 | 支持響應式編程 |
java8 | 語法豐富支持響應式編程,非堵塞式編程 |
spring5 | 新一代的web框架webflux,依託於servlet3.1+和java8 |
srpingboot2.x | 使用了spring5 |
Rxjava | 一種流行的響應式編程框架 |
Reactor | spring5中響應式編程的默認實現方式 |
響應式編程關鍵詞:
數據流:流式處理
異步: 異步處理
消息:基於消息名
- 客戶端先向服務器端註冊感興趣的event,完成了事件訂閱;
- 客戶端發生已經註冊的事件,會觸發服務器的響應,服務器存在一個selector線程,【輪詢客戶端發送過來的事件】可是並不實際處理事件,而是找到對應的Request Handler,啓用另一條線程運行處理。
- 最終結果會轉換成data stream,發送到客戶端;
基於servlet3.1對非阻塞機制,和java8的函數式語法,webflux出現了。
響應式編程分爲3層:
層 | 說明 |
---|---|
router functions | 路由分發層,reactor模式中的selector; 根據請求的事件,選擇對應的方法去處理客戶端發送過來的事件請求。 |
spring webflux | 控制層,處理業務邏輯前進行的封裝和控制數據流的返回格式 |
http/reactive streams | 轉換層:把結果轉換爲數據流的過程 |
容器要求:支持servlet3.1
java異步編程領域:Netty java
開發方式react
開發方式 | 說明 |
---|---|
類springMVC模式 | 簡單,跟普通的springMVC方式有不少共同點,容易被接受 |
函數功能性 | 使用的比較少,由於不太好理解,開發後端的技術若是出現兩種並存,效率不容易提升 |
數據流的封裝git
數據流封裝 | 說明 |
---|---|
Flux | 存放0-N個數據流序列,響應式框架會一個一個的發送到客戶端 |
Mono | 存放0-1個數據流序列,一次僅發送一個數據流序列到客戶端 |
跟springMVC對標
對比 | 說明 |
---|---|
DispatcherServlet | springMVC核心控制器 |
DispatcherHandler | webFlux核心處理器 |
核心處理代碼github
public Mono<Void> handle(ServerWebExchange exchange) { if (this.handlerMappings == null) { return createNotFoundError(); } return Flux.fromIterable(this.handlerMappings) .concatMap(mapping -> mapping.getHandler(exchange)) .next() .switchIfEmpty(createNotFoundError()) .flatMap(handler -> invokeHandler(exchange, handler)) .flatMap(result -> handleResult(exchange, result)); }
核心處理流程web
步驟 | 說明 |
---|---|
1 DispatherHandler | 接受請求 |
2 找到對應的HandlerMapping | 從控制器中獲得,經過@RequestMapping獲得 fromIterable(this.handlerMappings) |
3 獲得對應的HandlerAdapter | .concatMap(mapping -> mapping.getHandler(exchange)) |
4 處理完畢以後獲得Result | .flatMap(handler -> invokeHandler(exchange, handler)) |
5 返回到DispatherHandler | |
6 轉換爲handleResult | 轉換爲對應的數據流 .flatMap(result -> handleResult(exchange, result)); |
spring webflux只支持spring data reactive的數據源;
而數據庫的開發每每是堵塞的,因此,spring data reactive並不能對數據庫的開發提供支持。
適用於 redis,mongodb等nosql數據庫
啓動器代碼redis
package com.springbootpractice.demo.webflux; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories; @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableReactiveMongoRepositories(basePackages = "com.springbootpractice.demo.webflux.dao.repository") public class DemoWebfluxApplication { public static void main(String[] args) { SpringApplication.run(DemoWebfluxApplication.class, args); } }
持久層代碼spring
package com.springbootpractice.demo.webflux.dao.repository; import com.springbootpractice.demo.webflux.dao.entity.User; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import org.springframework.stereotype.Repository; import reactor.core.publisher.Flux; /** * 說明:TODO * @author carter * 建立時間: 2020年01月15日 6:18 下午 **/ @Repository public interface UserRepository extends ReactiveMongoRepository<User,Long> { Flux<User> findByUserNameLikeAndNoteLike(String userName,String note); }
控制器代碼
**sql
package com.springbootpractice.demo.webflux.controller; import com.springbootpractice.demo.webflux.core.UserValidator; import com.springbootpractice.demo.webflux.dao.entity.User; import com.springbootpractice.demo.webflux.service.UserService; import org.springframework.validation.DataBinder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import javax.validation.Valid; /** * 說明:TODO * @author carter * 建立時間: 2020年01月15日 6:43 下午 **/ @RestController public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @GetMapping(path = "/user/{id}") public Mono<User> getUser(@PathVariable("id") Long id){ return userService.getUserById(id); } @GetMapping(path = "/user/{userName}/{note}") public Flux<User> getUser(@PathVariable("userName") String userName, @PathVariable("note") String note){ return userService.findByUserNameAndNote(userName,note); } @GetMapping(path = "/user/insert/{user}") public Mono<User> insertUser(@Valid @PathVariable("user") User user){ return userService.insertUser(user); } // @InitBinder // public void initBinder(DataBinder binder){ // binder.setValidator(new UserValidator()); // } }
類比springMVC,提供了 WebFluxConfigurer進行配置,根據須要實現對應的方法;
對標 springMVC,也須要實現Converter接口:
代碼mongodb
package com.springbootpractice.demo.webflux.core; import com.springbootpractice.demo.webflux.dao.entity.User; import com.springbootpractice.demo.webflux.dao.entity.enums.SexEnum; import org.springframework.core.convert.converter.Converter; import org.springframework.util.Assert; import java.util.Objects; /** * 說明:TODO * @author carter * 建立時間: 2020年01月16日 9:28 上午 **/ public class String2UserConverter implements Converter<String, User> { @Override public User convert(String s) { final String[] split = Objects.requireNonNull(s,"轉換爲User的string不能爲空").split("-"); Assert.isTrue(split.length==4,"轉換爲User的string必須含有4個字段"); return User.builder() .id(Long.parseLong(split[0])) .userName(split[1]) .note(split[2]) .sex(SexEnum.getSexEnum(Integer.parseInt(split[3]))) .build(); } }
對標springMVC的校驗器, 實現Validator接口;
代碼數據庫
package com.springbootpractice.demo.webflux.core; import com.springbootpractice.demo.webflux.dao.entity.User; import org.apache.logging.log4j.util.Strings; import org.springframework.validation.Errors; import org.springframework.validation.Validator; /** * 說明:TODO * @author carter * 建立時間: 2020年01月16日 9:48 上午 **/ public class UserValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return clazz.equals(User.class); } @Override public void validate(Object target, Errors errors) { User user = (User) target; if (Strings.isBlank(user.getUserName())){ errors.rejectValue("userName","","用戶名不能爲空"); } } }
局部校驗器
對標springMVC, 能夠在控制器中增長 @InitBinder ,裏面配置好本控制器的校驗器
代碼
@InitBinder public void initBinder(DataBinder binder){ binder.setValidator(new UserValidator()); }
一些文件,圖片,配置內容的配置,能夠在WebConfigurer中進行配置;
代碼
package com.springbootpractice.demo.webflux.core; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.http.CacheControl; import org.springframework.validation.Validator; import org.springframework.web.reactive.config.ResourceHandlerRegistry; import org.springframework.web.reactive.config.WebFluxConfigurer; import java.util.concurrent.TimeUnit; /** * 說明:TODO * @author carter * 建立時間: 2020年01月16日 9:33 上午 **/ @Configuration public class WebFluxConfig implements WebFluxConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new String2UserConverter()); } @Override public Validator getValidator() { return new UserValidator(); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**") .addResourceLocations("classpath:/static/") .setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS)); } }
訪問靜態資源例子:
經過本篇文章,你學會了:
- springWebFlux的基礎概念和reactor模型;
- 一個利用webFlux操做mongodb的增刪改查的例子;
- 類比springMVC,開發webflux的轉換器,校驗器,靜態資源;
原創不易,轉載請註明出處,歡迎溝通交流。