1
Reactor is a fully non-blocking JVM based reactive programming with efficient demand management (in the form of back pressure). It integrates directly with Java8's functional APIs, especially CompletableFuture , Stream , and Duration . Provides a composable asynchronous serialization API — Flux (for [N] elements) and Mono (for [0|1] elements) — and broadly implements the reactive Stream specification.

This time, I will take you from scratch to build a Reactor responsive project using the Spring Boot framework.

1 Create a project

Use https://start.spring.io/ to create a project. Add dependencies: H2, Lombok, Spring Web, JPA, JDBC

Then import Reactor package

 <dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
</dependency>
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
</dependency>

2 Integrate H2 database

application.properties Add H2 data connection information to the file. Also, the port uses 8081 (optional, a port that is not used locally will do).

 server.port=8081
################ H2 数据库 基础配置 ##############
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:~/user
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.database=h2
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.path=/h2-console
spring.h2.console.enable=true

3 Create a test class

3.1 user entity

Create a simple data manipulation entity User.

 import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

/**
 * @Author: prepared
 * @Date: 2022/8/29 21:40
 */
@Data
@NoArgsConstructor
@Table(name = "t_user")
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String userName;

    private int age;

    private String sex;

    public User(String userName, int age, String sex) {
        this.userName = userName;
        this.age = age;
        this.sex = sex;
    }
}

3.2 UserRepository

The data model layer uses the JPA framework.

 import com.prepared.user.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * @Author: prepared
 * @Date: 2022/8/29 21:45
 */
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

3.3 UserService

The service adds two methods, the add method, which is used to add data; the list method, which is used to query all data. All interfaces return Mono/Flux objects.

Best practice: All third-party interfaces and IO-consuming operations can be placed in Mono objects.

doOnError Monitor abnormal conditions;

doFinally Monitor the overall execution, such as: time consumption, call volume monitoring, etc.

 import com.prepared.user.dao.UserRepository;
import com.prepared.user.domain.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Author: prepared
 * @Date: 2022/8/29 21:45
 */
@Service
public class UserService {

    private Logger logger = LoggerFactory.getLogger(UserService.class);

    @Resource
    private UserRepository userRepository;

    public Mono<Boolean> save(User user) {
        long startTime = System.currentTimeMillis();
        return Mono.fromSupplier(() -> {
                    return userRepository.save(user) != null;
                })
                .doOnError(e -> {
                    // 打印异常日志&增加监控(自行处理)
                    logger.error("save.user.error, user={}, e", user, e);
                })
                .doFinally(e -> {
                    // 耗时 & 整体健康
                    logger.info("save.user.time={}, user={}", user, System.currentTimeMillis() - startTime);
                });
    }

    public Mono<User> findById(Long id) {
        long startTime = System.currentTimeMillis();
        return Mono.fromSupplier(() -> {
                    return userRepository.getReferenceById(id);
                }).doOnError(e -> {
                    // 打印异常日志&增加监控(自行处理)
                    logger.error("findById.user.error, id={}, e", id, e);
                })
                .doFinally(e -> {
                    // 耗时 & 整体健康
                    logger.info("findById.user.time={}, id={}", id, System.currentTimeMillis() - startTime);
                });
    }

    public Mono<List<User>> list() {
        long startTime = System.currentTimeMillis();
        return Mono.fromSupplier(() -> {
                    return userRepository.findAll();
                }).doOnError(e -> {
                    // 打印异常日志&增加监控(自行处理)
                    logger.error("list.user.error, e", e);
                })
                .doFinally(e -> {
                    // 耗时 & 整体健康
                    logger.info("list.user.time={}, ", System.currentTimeMillis() - startTime);
                });
    }
  
  public Flux<User> listFlux() {
        long startTime = System.currentTimeMillis();
        return Flux.fromIterable(userRepository.findAll())
                .doOnError(e -> {
                    // 打印异常日志&增加监控(自行处理)
                    logger.error("list.user.error, e", e);
                })
                .doFinally(e -> {
                    // 耗时 & 整体健康
                    logger.info("list.user.time={}, ", System.currentTimeMillis() - startTime);
                });
    }
}

3.4 UserController

controller Add two methods, add method, used to add data; list method, used to query all data.

There is another way of writing the list method, which involves the difference between Mono and Flux.

Return List can use Mono<List<User>> or Flux<User> .

  • Mono<T> is a specific Publisher<T> that can emit at most one element
  • Flux<T> is a standard Publisher<T> expressed as an asynchronous sequence that emits 0 to N elements
 import com.prepared.user.domain.User;
import com.prepared.user.service.UserService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author: prepared
 * @Date: 2022/8/29 21:47
 */
@RestController
public class UserController {

    @Resource
    private UserService userService;

    @RequestMapping("/add")
    public Mono<Boolean> add() {
        User user = new User("xiaoming", 10, "F");
        return userService.save(user) ;

    }

    @RequestMapping("/list")
    public Mono<List<User>> list() {
        return userService.list();
    }
}

    @RequestMapping("/listFlux")
    public Flux<User> listFlux() {
        return userService.listFlux();
    }

3.5 SpringReactorApplication adds annotation support

Add annotations to the Application startup class @EnableJpaRepositories

 import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

/**
 * Hello world!
 */
@SpringBootApplication
@EnableJpaRepositories
public class SpringReactorApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringReactorApplication.class, args);
    }
}

test

Start the project, visit localhost:8081/add , and return true normally.

Query all data, visit localhost:8081/list , you can see the inserted data, which has been queried. PS: I have performed multiple add here, so there are multiple records.

Background log:

 2022-09-05 20:13:17.385  INFO 15696 --- [nio-8082-exec-2] com.prepared.user.service.UserService    : list.user.time=181,

The doFinnally code block of the UserService list() method is executed, and the time-consuming log is printed.

Summarize

The advantage of reactive programming is that it does not block. So what are the blocking operations in our code?

  1. Future get() method;
  2. The Reactor block() method in ---c3d9bda7c1d049dc69ab6f966f7344cd---, subcribe() method, so when using Reactor, unless you write test code, do not directly call the above two methods;
  3. Synchronous method calls, so in the case of high concurrency, asynchronous calls (such as Future) will be used to improve the response speed.

The next article will explain how to integrate the fuse and current limiting framework resilience4j into the project, so stay tuned.


程序员伍六七
201 声望597 粉丝