认识 WebFlux
什么是 WebFlux
- 用于构建基于 Reactive 技术栈之上的 Web 应用程序
- 基于 Reactive Streams API ,运行在非阻塞服务器上
为什么会有 WebFlux
-
对于非阻塞 Web 应用的需要
-
函数式编程(Java8中引入的lambda表达式)
关于 WebFlux 的性能
- 单次请求的耗时并不会有很大的改善
- 仅需少量固定数量的线程和较少的内存即可实现扩展,并发的处理,提升并发的容量(基于Reactive )
WebMVC 和 WebFlux 应用场景
- 已有 Spring MVC 应用,运行正常,就别改了
- 依赖了大量阻塞式持久化 API 和网络 API,比如说,使用mysql,建议使用 Spring MVC
- 已经使用了非阻塞技术栈(使用redis MongoDB),可以考虑使用 WebFlux
- 想要使用 Java 8 Lambda 结合轻量级函数式框架,可以考虑 WebFlux
WebFlux 中的编程模型
两种编程模型
- 基于注解的控制器器
- 函数式 Endpoints
基于注解的控制器
常用注解
- @Controller @RestController
- @RequestMapping 及其等价注解(delete put get post)
- @RequestBody / @ResponseBody (结果和请求的正文)
返回值
- Mono(0或者1个返回) / Flux(多个返回)
例子
目录结构如下:
代码:
@Controller
@RequestMapping("/coffee")
@Slf4j
public class CoffeeController {
@Autowired
private CoffeeService coffeeService;
@GetMapping(path = "/", params = "!name")
@ResponseBody
public Flux<Coffee> getAll() {
return coffeeService.findAll();
}
@RequestMapping(path = "/{id}", method = RequestMethod.GET)
@ResponseBody
public Mono<Coffee> getById(@PathVariable Long id) {
return coffeeService.findById(id);
} //@PathVariable 将id注入
@GetMapping(path = "/", params = "name")
@ResponseBody
public Mono<Coffee> getByName(@RequestParam String name) {
return coffeeService.findByName(name);
}
}
@RestController
@RequestMapping("/order")
@Slf4j
public class CoffeeOrderController {
@Autowired
private OrderService orderService;
@Autowired
private CoffeeService coffeeService;
@GetMapping("/{id}")
public Mono<CoffeeOrder> getOrder(@PathVariable("id") Long id) { //只是将返回变了
return orderService.getById(id);
}
@PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseStatus(HttpStatus.CREATED)
public Mono<CoffeeOrder> create(@RequestBody NewOrderRequest newOrder) {
log.info("Receive new Order {}", newOrder);
return orderService.create(newOrder.getCustomer(), newOrder.getItems())
.flatMap(id -> orderService.getById(id));
}
}
@Repository
public class CoffeeOrderRepository {
@Autowired
private DatabaseClient databaseClient;
public Mono<CoffeeOrder> get(Long id) {
return databaseClient.execute()
.sql("select * from t_order where id = " + id)
.map((r, rm) ->
CoffeeOrder.builder()
.id(id)
.customer(r.get("customer", String.class))
.state(OrderState.values()[r.get("state", Integer.class)])
.createTime(r.get("create_time", Date.class))
.updateTime(r.get("update_time", Date.class))
.items(new ArrayList<Coffee>())
.build() //用map方式 把order对象拼装出来
)
.first()
.flatMap(o ->
databaseClient.execute()
.sql("select c.* from t_coffee c, t_order_coffee oc " +
"where c.id = oc.items_id and oc.coffee_order_id = " + id)
.as(Coffee.class)
.fetch()//将FetchSpec转换为Flux
.all()
.collectList()//取出所有的 转换成Mono<List<Coffee>>
.flatMap(l -> {
o.getItems().addAll(l);
return Mono.just(o); //这个方法的返回为Mono
})
);
}
public Mono<Long> save(CoffeeOrder order) {
return databaseClient.insert().into("t_order")
.value("customer", order.getCustomer())
.value("state", order.getState().ordinal())
.value("create_time", new Timestamp(order.getCreateTime().getTime()))
.value("update_time", new Timestamp(order.getUpdateTime().getTime()))
.fetch()
.first()
.flatMap(m -> Mono.just((Long) m.get("ID"))) //根据ID注解取出id
.flatMap(id -> Flux.fromIterable(order.getItems())
.flatMap(c -> databaseClient.insert().into("t_order_coffee")
.value("coffee_order_id", id)
.value("items_id", c.getId())
.then()).then(Mono.just(id)));
}
}
public interface CoffeeRepository extends R2dbcRepository<Coffee, Long> {
@Query("select * from t_coffee where name=$1")
Mono<Coffee> findByName(String name);
}
@Service
public class CoffeeService {
@Autowired
private CoffeeRepository coffeeRepository;
public Mono<Coffee> findById(Long id) {
return coffeeRepository.findById(id);
}
public Flux<Coffee> findAll() {
return coffeeRepository.findAll();
}
public Mono<Coffee> findByName(String name) {
return coffeeRepository.findByName(name);
}
}
@Service
public class OrderService {
@Autowired
private CoffeeOrderRepository orderRepository;
@Autowired
private CoffeeRepository coffeeRepository;
public Mono<CoffeeOrder> getById(Long id) {
return orderRepository.get(id);
}
public Mono<Long> create(String customer, List<String> items) {
return Flux.fromStream(items.stream()) //将所有的咖啡名转为Flux<String>
.flatMap(n -> coffeeRepository.findByName(n))
.collectList()//转换为 Mono List<Coffee>
.flatMap(l ->
orderRepository.save(CoffeeOrder.builder()
.customer(customer)
.items(l)
.state(OrderState.INIT)
.createTime(new Date())
.updateTime(new Date())
.build())
);
}
}
@SpringBootApplication
@EnableR2dbcRepositories
public class WaiterServiceApplication extends AbstractR2dbcConfiguration {
public static void main(String[] args) {
SpringApplication.run(WaiterServiceApplication.class, args);
}
@Bean
public ConnectionFactory connectionFactory() {
return new H2ConnectionFactory( //连接h2
H2ConnectionConfiguration.builder()
.inMemory("testdb")
.username("sa")
.build());
}
@Bean
public R2dbcCustomConversions r2dbcCustomConversions() {
Dialect dialect = getDialect(connectionFactory());
CustomConversions.StoreConversions storeConversions =
CustomConversions.StoreConversions.of(dialect.getSimpleTypeHolder());
return new R2dbcCustomConversions(storeConversions,
Arrays.asList(new MoneyReadConverter(), new MoneyWriteConverter()));
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonBuilderCustomizer() {
return builder -> builder.indentOutput(true)
.timeZone(TimeZone.getTimeZone("Asia/Shanghai"));
}
}