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, especiallyCompletableFuture
,Stream
, andDuration
. 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 specificPublisher<T>
that can emit at most one element -
Flux<T>
is a standardPublisher<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?
-
Future
get()
method; - 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; - 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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。