webflux

本文围绕Web-Flux展开,介绍了Mono、Flux案例,对比了Mono与常规类型处理请求的响应时间。阐述了SSE技术,包括其定义、数据类型,并给出demo。还讲述了Web-Flux与mongodb的整合,涵盖entity、repository等方面,最后提供了源码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Web-Flux

Mono案例

  • 依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
    
  • controller

    @RestController
    @Slf4j
    public class FluxController {
    
        @GetMapping("/mono")
        public Mono<String> hello() {
            // Mono 表示: >=0 个元素的异步序列
            return Mono.just("mono hello");
        }
    
    
    
    }
    
  • 比较Mono 和常规的类型

    • 比较内容:发送请求后的直接相应时间,具体代码如下。
    package com.huifer.webflux.controller;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import reactor.core.publisher.Mono;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * 描述:
     *
     * @author huifer
     * @date 2019-06-02
     */
    @RestController
    @Slf4j
    public class MonoController {
    
        @GetMapping("/default")
        public String defaultResp() {
            log.info("defaultResp method start");
            String default_hello = doSome("default hello");
            log.info("defaultResp method end");
    
            return default_hello;
        }
    
    
        @GetMapping("/mono")
        public Mono<String> mono() {
            log.info("mono method start");
            Mono<String> mono_hello = Mono.fromSupplier(() -> doSome("mono hello"));
            log.info("mono method end");
            return mono_hello;
    
        }
    
    
        private String doSome(String msg) {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            return msg;
        }
    
    
        
    }
    
    
    2019-06-02 15:38:05.432  INFO 6076 --- [ctor-http-nio-2] c.h.webflux.controller.FluxController    : defaultResp method start
    2019-06-02 15:38:08.433  INFO 6076 --- [ctor-http-nio-2] c.h.webflux.controller.FluxController    : defaultResp method end
    
    
    
    2019-06-02 15:38:11.907  INFO 6076 --- [ctor-http-nio-3] c.h.webflux.controller.FluxController    : mono method start
    2019-06-02 15:38:11.908  INFO 6076 --- [ctor-http-nio-3] c.h.webflux.controller.FluxController    : mono method end
    
    • 不难看出defaultResp方法他必须要等待3秒才能有结果返回,而mono 是直接就返回了,mono的处理内容会交给其他线程进行操作。用户访问请求还是需要等待3秒并不是不需要等待!!!
请求1 defaultResp 请求1结束 请求2 请求2结束 请求进入堵塞状态 等待3秒返回结果 进入堵塞状态 等待请求1结束 得到结果 请求1 defaultResp 请求1结束 请求2 请求2结束
请求1 monoMethod 结果处理器 请求1结束 请求2 请求2结束 请求发送 处理结果 返回请求1数据 直接返回值等待时间3秒 请求发送 处理结果 返回请求2数据 直接返回结果等待时间3秒 请求1 monoMethod 结果处理器 请求1结束 请求2 请求2结束

Flux 案例

package com.huifer.webflux.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;

@RestController
@Slf4j
public class FluxController {

    @GetMapping("/flux")
    public Flux<String> flux1(@RequestParam String[] lang) {
        return Flux.fromArray(lang);
    }

    @GetMapping("/flux2")
    public Flux<String> flux2(@RequestParam List<String> lang) {

        return Flux.fromStream(lang.stream());
    }

    /**
     * 返回给JS进行操作并不是直接给用户查看
     *
     * @return
     */
    @GetMapping(value = "/see", produces = "text/event-stream")
    public Flux<String> flux3() {
        return Flux.just("java", "python");
    }

}

SSE 案例

什么是SSE

SSE ( Server-sent Events )是 WebSocket 的一种轻量代替方案,使用 HTTP 协议。

  • Server-Sent Events:简称SSE技术,也是前端es5支持的一种基于http协议的服务器推送技术。
  • EventSource:js中承载SSE技术的主要核心方案和方法
  • 单工通道:只能一方面的数据导向,例如,在我们前端,我们通过浏览器向服务器请求数据,但是服务器不会主动推送数据给我们,这就是单通道
  • 双工通道:类似webSocket这样的技术,客户端可以推数据给服务器,服务器也可以推数据给客户端
  • 定点推送:服务器可以将数据针对单个客户端推送
  • 多人推送:服务器可以将数据一次性推送给所有人

SSE数据类型

data: first event
 
data: second event
id: 100
 
event: myevent
data: third event
id: 101
 
: this is a comment
data: fourth event
data: fourth event continue
  • 类型为空白,表示该行是注释,会在处理时被忽略。
  • 类型为 data,表示该行包含的是数据。以 data 开头的行可以出现多次。所有这些行都是该事件的数据。
  • 类型为 event,表示该行用来声明事件的类型。浏览器在收到数据时,会产生对应类型的事件。
  • 类型为 id,表示该行用来声明事件的标识符。
  • 类型为 retry,表示该行用来声明浏览器在连接断开之后进行再次连接之前的等待时间。

demo

  • 使用SSE

    @WebServlet("/notsse")
    public class NotSseServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
            PrintWriter writer = resp.getWriter();
    
    
            for (int i = 0; i < 5; i++) {
                writer.print("data:" + i + "\n");
                writer.print("\n");
                writer.flush();
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }
        }
    }
    

nosse

  • 使用SSE

    @WebServlet(value = "/sse/default",asyncSupported = true)
    public class DefaultEventServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
            resp.setContentType("text/event-stream");
            resp.setCharacterEncoding("utf-8");
            PrintWriter writer = resp.getWriter();
    
    
            for (int i = 0; i < 5; i++) {
                writer.print("data:" + i + "\n");
                writer.print("\n");
                writer.flush();
    
    
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }
        }
    }
    

    sse

  • 使用SSE 的结果返回为一步步的返回, 不适用为等待所有处理完成后返回…

整合mongodb

  • entity

    @Data
    @Document(collection = "t_student")
    public class Student {
    
        @Id
        private String id;
    
    
        @NotBlank(message = "名字不能空")
        private String name;
    
        @Range(max = 200, min = 18)
        private int age;
    
    
    }
    
  • repository

    public interface StudentRepository
            extends ReactiveMongoRepository<Student, String> {
    
    
        /***
         * 根据年龄上下限查询 ,都不包含边界
         * @param below 下限
         * @param top 上限
         * @return
         */
        Flux<Student> findByAgeBetween(int below, int top);
    
    
    }
    
  • controller

    package com.huifer.webflux.controller;
    
    import com.huifer.webflux.entity.Student;
    import com.huifer.webflux.repository.StudentRepository;
    import com.huifer.webflux.util.NameValidateUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import javax.validation.Valid;
    
    
    @RestController
    @RequestMapping("/student")
    public class StudentController {
    
    
        @Autowired
        private StudentRepository studentRepository;
    
        @GetMapping("/all")
        public Flux<Student> all() {
            return studentRepository.findAll();
        }
    
        @GetMapping(value = "/sse/all", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<Student> allSSE() {
            return studentRepository.findAll();
        }
    
        @PostMapping("/update")
        public ResponseEntity add(@Valid Student student) {
            return ResponseEntity.ok(studentRepository.save(student));
        }
    
    
        @PostMapping("/save")
        public ResponseEntity save(@Valid Student student) {
            NameValidateUtil.validateName(student.getName());
    
            return ResponseEntity.ok(studentRepository.save(student));
        }
    
    
        @DeleteMapping("/del/{id}")
        public Mono<Void> deleteStudentVoid(@PathVariable(value = "id") String id) {
            // 无状态删除 不管是否成功 都是200
            return studentRepository.deleteById(id);
        }
    
        @DeleteMapping("/ddl/{id}")
        public Mono<ResponseEntity> deleteStudent(@PathVariable(value = "id") String id) {
            Mono<ResponseEntity> responseEntityMono = studentRepository.findById(id).flatMap(
                    student -> studentRepository.delete(student)
                            // 成功
                            .then(Mono.just(ResponseEntity.ok().body(student)))
                            // 失败
                            .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND))
            );
    
            return responseEntityMono;
        }
    
    
        @PutMapping("/put/{id}")
        public Mono<ResponseEntity<Student>> update(@PathVariable(value = "id") String id, @RequestBody Student student) {
    
            Mono<ResponseEntity<Student>> responseEntityMono = studentRepository.findById(id).flatMap(
                    stu -> {
                        stu.setAge(student.getAge());
                        stu.setName(student.getName());
                        return studentRepository.save(stu);
                    }).map(stu -> new ResponseEntity<Student>(stu, HttpStatus.OK))
    
                    .defaultIfEmpty(new ResponseEntity<Student>(HttpStatus.NOT_FOUND));
    
    
            return responseEntityMono;
        }
    
    
        @GetMapping("/find/{id}")
        public Mono<ResponseEntity<Student>> findById(@PathVariable("id") String id) {
    
            return studentRepository.findById(id).map(stu -> new ResponseEntity<Student>(stu, HttpStatus.OK))
                    .defaultIfEmpty(new ResponseEntity<Student>(HttpStatus.NOT_FOUND));
        }
    
    
        @GetMapping("/age/{below}/{top}")
        public Flux<Student> findByAge(@PathVariable("below") int below, @PathVariable("top") int top) {
            return studentRepository.findByAgeBetween(below, top);
        }
    
    
    }
    
  • exception

    @Getter
    @Setter
    @NoArgsConstructor
    public class StudentException extends RuntimeException {
        private String errField;
        private String errValue;
    
        public StudentException(String message, String errField, String errValue) {
            super(message);
            this.errField = errField;
            this.errValue = errValue;
        }
    }
    
  • advice

    @ControllerAdvice
    public class StucentAop {
    
        private static String apply(String s1, String s2) {
            return s1 + "\n" + s2;
        }
    
        @ExceptionHandler
        public ResponseEntity validateHandleName(StudentException ex) {
            return new ResponseEntity(ex.getMessage(), HttpStatus.BAD_REQUEST);
        }
    
    
        @ExceptionHandler
        public ResponseEntity validateHandle(WebExchangeBindException ex) {
            return new ResponseEntity(ex2str(ex), HttpStatus.BAD_REQUEST);
        }
    
        private String ex2str(WebExchangeBindException ex) {
    
            return ex.getFieldErrors().stream().map(
                    e -> {
                        return e.getField() + " : " + e.getDefaultMessage();
                    }
            ).reduce("", StucentAop::apply);
    
    
        }
    
    
    }
    
  • util

    public class NameValidateUtil {
    
    
        public static final String[] INVALIDE_NAMES = {"ADMIN", "ROOT"};
    
        public static void validateName(String name) {
    
            Arrays.stream(INVALIDE_NAMES).filter(s -> name.equalsIgnoreCase(s))
                    .findAny()
                    .ifPresent(s -> {
                        throw new StudentException("name 非法名词", s, "非法名词");
                    });
    
            ;
    
        }
    }
    
  • 启动项

    @SpringBootApplication
    @EnableReactiveMongoRepositories
    public class WebFluxApp {
        public static void main(String[] args) {
            SpringApplication.run(WebFluxApp.class, args);
        }
    }
    

源码

本文代码及可视化代码均放在 GITHUB 欢迎star & fork

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值