如何使用Spring Boot Webflux

437 阅读6分钟

Spring Boot Webflux入门

反应式编程支持异步的、事件驱动的、非阻塞的数据处理方法。它将事件和数据组织成流。

在反应式编程范式中,当一个请求被提出时,其他任务在等待结果时被执行。

当数据可用时,通过回调函数发送通知和结果。

因此,反应式编程范式适用于数据驱动的应用程序,如聊天应用程序。

在本教程中,我们将使用Spring Webflux和MongoDB创建一个学生管理系统。

先决条件

  • 在你的计算机上安装[JDK]。
  • 安装了你喜欢的IDE或编辑器。
  • 对[Java]和[Spring Boot]的了解。
  • 对[MongoDB]的了解。

流媒体API

Netflix、Twitter、Pivotal以及Redhat的软件开发人员合作创建了流API。Streams API定义了四个接口,讨论如下。

发布者

发布者接口根据发送的请求向订阅者发射事件。因此,一个发布者可以为多个订阅者服务。

public interface Publisher<T> 
{
    public void subscribe(Subscriber<? super T> s);
}

订阅者

Subscriber 接口监听并接收来自Publisher 接口的事件。Subscriber 接口有四个方法来处理来自Publisher 接口的响应。

public interface Subscriber<T> 
{
    public void onSubscribe(Subscription s);
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();
}

订阅者

Subscription 接口定义了PublisherSubscriber 接口之间的一对一关系。它可以用来请求数据,也可以用来取消请求。

public interface Subscription<T> 
{
    public void request(long n);
    public void cancel();
}

处理器

Processor 接口代表了包含PublisherSubscriber 的处理阶段。

public interface Processor<T, R> extends Subscriber<T>, Publisher<R>{

}

反应式流的两个流行实现是RxJavaProject reactor

Spring Webflux

Spring Webflux与Spring MVC类似,但它支持反应式和非阻塞流。

Spring Webflux有两个发布器。

Mono

Mono 是一个发布器,可以返回 或 元素。0 1

Mono<String> mono = Mono.just("Jonh");
Mono<String> mono = Mono.empty();

Flux

Flux 是一个发布者,它发射 或 元素。0 N

Flux<String> flux = Flux.just("x", "y", "z");
Flux<String> flux = Flux.fromArray(new String[]{"x", "y", "z"});
Flux<String> flux = Flux.fromIterable(Arrays.asList("x", "y", "z"));
 
// To subscribe, call the method
 
flux.subscribe();

应用程序设置

我们将使用spring initializr来生成我们的应用程序启动代码。

  1. 在你的网络浏览器上,导航到spring initializr

  2. 输入组别为io.section ,名称为webfluxexample

  3. 添加Spring webflux,Mongo reactive, 和Lombok 作为项目依赖。

  4. 点击生成,下载启动项目文件的压缩包。

  5. 解压缩文件并在你喜欢的代码编辑器或IDE中打开项目。

  6. pom.xml 文件中添加下面的依赖项。

    <dependencies>
      <dependency>
         <groupId>javax.xml.bind</groupId>
         <artifactId>jaxb-api</artifactId>
         <version>2.3.0</version>
      </dependency>
      <dependency>
         <groupId>javax.servlet</groupId>
         <artifactId>javax.servlet-api</artifactId>
         <version>3.1.0</version>
         <scope>provided</scope>
      </dependency>
    </dependencies>
    

配置层

在我们之前创建的config 包中创建一个名为MongoConfig.java 的新文件,然后添加下面的代码片断。

@Configuration
@EnableMongoRepositories(basePackages = "io.section.webfluxexample.repositories")
public class MongoDBConfig extends AbstractReactiveMongoConfiguration {

    @Value("${database.name}")
    private String databaseName;

    @Value("${database.host}")
    private String databaseHost;


    @Override
    protected String getDatabaseName() {
        return databaseName;
    }

    @Override
    public MongoClient reactiveMongoClient() {
        String name = databaseHost;
        return MongoClients.create(name);
    }

    @Bean
    public ReactiveMongoTemplate reactiveMongoTemplate() {
        return new ReactiveMongoTemplate(reactiveMongoClient(), getDatabaseName());
    }
}

config 包中,创建一个名为WebFluxConfig.java 的新文件,并添加下面的代码片段。

@Configuration // Marks the class as configuration class
@EnableWebFlux // Enables Webflux in our application
public class WebFluxConfig implements WebFluxConfigurer {
}

在资源目录中,在applications.properties 文件中添加下面的代码片断。

database.name=myFirstDatabase # database name property
database.host = mongodb+srv://<username>:<password>@cluster0.mk0n7.gcp.mongodb.net/myFirstDatabase?retryWrites=true&w=majority #database connection string from mongo atlas

数据层

在根项目包中,创建一个名为model 的新包。

在上面创建的model 包中,创建一个名为Student.java 的新文件,并添加下面的代码片断。

@Scope(scopeName = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Document // Marks this class as a MongoDB document
@Data // Lombok annotation to generate getters, setters, toString, and equals methods
public class Student {
    @Id
    private int id;
    private String name;
    private String course;
}

存储库层

在根项目目录下,创建一个名为repository 的新包。

在生成的repository 包中创建一个名为StudentRepository.java 的新文件。

public interface StudentRepository extends ReactiveMongoRepository<Student, Integer> {
    @Query("{ 'name': ?0 }")
    Flux<Student> findByName(final String name); // Flux returns zero or n elements
}

服务层

在根项目包中,创建一个名为service 的新包。

创建一个名为StudentService.java 的文件并添加下面的代码片段。

public interface StudentService {
    void createStudent(Student student); // Returns null after creating a student

    Mono<Student> findById(int id); // Returns 0 or a single student

    Flux<Student> findByName(String name); // Returns a list of students whose names match the searched name

    Flux<Student> findAll(); // Returns all students

    Mono<Student> update(Student student, int id); // Updates and returns the updated student

    Mono<Void> delete(int id); // Delete the student

}

service 包中,创建一个名为StudentServiceImpl.java 的新文件,并添加下面的代码片断。

@Service
@AllArgsConstructor
public class StudentServiceImpl implements StudentService {
    private final StudentRepository repository;
    // Saves the student into the database
    @Override
    public void createStudent(Student student) {

        repository.insert(student).subscribe();
    }
    // Finds a single student by id
    @Override
    public Mono<Student> findById(int id) {
        return repository.findById(id);
    }
    // Finds a list of students whose names match the searched name
    @Override
    public Flux<Student> findByName(String name) {
        return repository.findByName(name);
    }
    // Returns a list of all students from the database
    @Override
    public Flux<Student> findAll() {
        return repository.findAll();
    }
    // Saves a student into the database
    @Override
    public Mono<Student> update(Student student, int id) {
        return repository.findById(id) // tries to get a student with the specified id
                .doOnError(IllegalStateException::new) 
                .map(studentMap -> {
                    studentMap.setName(student.getName());
                    studentMap.setCourse(student.getCourse());
                    return studentMap;
                }).flatMap(repository::save); // Updates the student with the id passed if the student is present in the database
    }
    // Deletes a student from the database
    @Override
    public Mono<Void> delete(int id) {
        return repository.deleteById(id);
    }
}

控制器层

在根项目包中,创建一个名为controllers 的新包。

在上面创建的controllers 包中创建一个名为StudentController.java 的新文件。

@RestController // Marks this class as a REST controller
@RequestMapping("/api/students") // Sets the base URL for students API
@AllArgsConstructor // Lombok annotation to generates a constructor for the class
public class StudentController {
    private final StudentService service;
    // Handles the student creation POST request.
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void createStudent(Student student) {
        service.createStudent(student);
    }
    // Handles get student by id endpoint
    @GetMapping("/id/{id}")
    public ResponseEntity<Mono<Student>> getById(@PathVariable("id") int id) {
        Mono<Student> student = service.findById(id);
        HttpStatus status = student != null ? HttpStatus.OK : HttpStatus.NOT_FOUND;
        return new ResponseEntity<>(student, status);
    }
    // Handles the search student by name endpoint
    @GetMapping("/name/{name}")
    public Flux<Student> getByName(@PathVariable("name") String name) {
        return service.findByName(name);
    }
    // Returns a list of students
    @GetMapping
    public Flux<Student> findAll() {
        return service.findAll();
    }
    // Updates the student with the provided id
    @PatchMapping("/{id}")
    public Mono<Student> updateStudent(@RequestBody Student student, @PathVariable("id") int id) {
        return service.update(student, id);
    }
    // Deletes the student with the provided id
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteStudent(@PathVariable("id") int id) {
        service.delete(id).subscribe();
    }
}

每当我们的应用程序启动时,我们需要用一些假的数据来填充数据库。

在应用程序类中,添加下面的代码片断。

@SpringBootApplication
public class WebFluxExampleApplication {
    //This block of code executes everytime the application starts
    @Bean
    CommandLineRunner employees(StudentRepository studentRepository) {
        return args -> studentRepository
                .deleteAll() // deletes all the records in the database
                .subscribe(null, null, () -> Stream.of(
                                new Student(1, "Samuel", "Computer science"),
                                new Student(2, "Dana", "Electrical engineering"),
                                new Student(3, "Paul", "Pure and Applied mathematics"),
                                new Student(4, "Denis", "Software engineering")
                        )
                        .forEach(student -> {
                            studentRepository
                                    .save(student) // saves all the new records in the database
                                    .subscribe(System.out::println);

                        }));

    }


    public static void main(String[] args) {
        SpringApplication.run(WebFluxExampleApplication.class, args);
    }

}

测试

我们将使用Postman测试该应用程序。

添加一个学生

在Postman上向http://localhost:8080/api/students 发出POST请求,并在请求体中传递以下JSON有效载荷。

{
    "name": "Job", //Student name
    "course": "Software engineering" //student
}

获取所有学生

在Postman上向http://localhost:8080/api/students 发出一个GET请求。

Get all students

通过ID获取学生

在Postman上向http://localhost:8080/api/students/id/2 发出一个GET请求。URL末尾的数字2 是学生的ID。

Get student by id

通过名字获取学生

在Postman上向http://localhost:8080/api/students/name/Denis发出GET请求。Denis 是学生的名字,其详细信息将被返回。

Getting student by name

更新学生信息

在Postman上向http://localhost:8080/api/students/2 发出PUT请求,在请求正文中传入以下JSON有效载荷。

{
    "id": 2,
    "name": "Diana",
    "course": "Electrical engineering"
}

Updating student details

删除一个学生

向postman上的http://localhost:8080/api/students/2 发出一个DELETE请求。URL末尾的数字2是要删除的学生的ID。

Deleting student

总结

通过阅读本文获得的知识,请尝试使用Spring Boot Webflux与您选择的任何前端客户端实现一个聊天系统。