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 接口定义了Publisher 和Subscriber 接口之间的一对一关系。它可以用来请求数据,也可以用来取消请求。
public interface Subscription<T>
{
public void request(long n);
public void cancel();
}
处理器
Processor 接口代表了包含Publisher 和Subscriber 的处理阶段。
public interface Processor<T, R> extends Subscriber<T>, Publisher<R>{
}
反应式流的两个流行实现是RxJava和Project 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来生成我们的应用程序启动代码。
-
在你的网络浏览器上,导航到spring initializr。
-
输入组别为
io.section,名称为webfluxexample。 -
添加
Spring webflux,Mongo reactive, 和Lombok作为项目依赖。 -
点击生成,下载启动项目文件的压缩包。
-
解压缩文件并在你喜欢的代码编辑器或IDE中打开项目。
-
在
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请求。

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

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

更新学生信息
在Postman上向http://localhost:8080/api/students/2 发出PUT请求,在请求正文中传入以下JSON有效载荷。
{
"id": 2,
"name": "Diana",
"course": "Electrical engineering"
}

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

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