WebFlux
在响应式编程中主要是基于spring5
与spring mvc相同
Spring WebFlux提供了一个基于注解的编程模型,@Controller和@RestController组件使用注解来表示请求映射、请求输入、处理异常等等。
带注释的控制器具有灵活的方法签名,无需扩展基类或实现特定的接口。
定义
特性 | Spring Web MVC | SpringWebFlux |
---|---|---|
应用控制器注解声明 | @Controller | 相同 |
应用REST控制器注解声明 | @RestController | 相同 |
映射
特性 | Spring Web MVC | SpringWebFlux |
---|---|---|
请求映射注解声明 | @RequestMapping | 相同 |
GET方法映射 | @GetMapping | 相同 |
POST方法映射 | @PostMapping | 相同 |
PUT方法映射 | @PutMapping | 相同 |
DELETE方法映射 | @DeleteMapping | 相同 |
PATCH方法映射 | @PatchMapping | 相同 |
请求
特性 | Spring Web MVC | SpringWebFlux |
---|---|---|
获取请求参数 | @RequestParam | 相同 |
获取请求头 | @RequestHeader | 相同 |
获取Cookie值 | @CookieValue | 相同 |
获取完整请求主体内容 | @RequestBody | 相同 |
获取请求路径变量 | @PathVariable | 相同 |
获取请求内容(包括请求主体和请求头) | RequestEntity | 相同 |
响应
特性 | Spring Web MVC | SpringWebFlux |
---|---|---|
响应主体注解声明 | @ResponseBody | 相同 |
响应内容(包括响应主体和响应头) | @ResponseEntity | 相同 |
响应Cookie内容 | @ResponseCookie | 相同 |
拦截
特性 | Spring Web MVC | SpringWebFlux |
---|---|---|
@Controller注解切面通知 | @ControllerAdvice | 相同 |
@ResetController注解切面通知 | @RestControlerAdvice | 相同 |
初识WebFlux
@GetMapping("/1")
public String get1(){
log.info("get1 start");
String string = createStr();
log.info("get1 end");
return string+"1";
}
private String createStr() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "some string";
}
//Mono 0-1个
@GetMapping("/2")
public Mono<String> get2(){
log.info("get2 start");
Mono<String> result = Mono.fromSupplier(()->createStr()+"2");
log.info("get2 end");
return result;
}
//Flux 0-N个
@GetMapping(value = "/3",produces = "text/event-stream")
public Flux<String> get3(){
log.info("get3 start");
Flux<String> result = Flux.fromStream(IntStream.range(1,5).mapToObj(i->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "flux data--" + i;
}
));
log.info("get3 end");
return result;
}
@GetMapping(value = "/33", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
private Flux<String> flux() {
Flux<String> result = Flux
.fromStream(IntStream.range(1, 5).mapToObj(i -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
return "flux data--" + i;
}));
return result;
}
这是结果
2022-02-09 21:58:10.955 INFO 6524 — [nio-8080-exec-7] c.e.d.Servlet.RestController : get1 start
2022-02-09 21:58:15.963 INFO 6524 — [nio-8080-exec-7] c.e.d.Servlet.RestController : get1 end
2022-02-09 21:58:19.800 INFO 6524 — [nio-8080-exec-9] c.e.d.Servlet.RestController : get2 start
2022-02-09 21:58:19.806 INFO 6524 — [nio-8080-exec-9] c.e.d.Servlet.RestController : get2 end
2022-02-09 21:58:33.030 INFO 6524 — [nio-8080-exec-2] c.e.d.Servlet.RestController : get3 start
2022-02-09 21:58:33.034 INFO 6524 — [nio-8080-exec-2] c.e.d.Servlet.RestController : get3 end
可以很清楚的发现webFlux使用了非阻塞的方式
应用
webFlux与webMvc最大的区别就是他无法使用关系型数据库
所以在应用中我们都是连接MongoDb
引入依赖和配置文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
spring:
data:
mongodb:
# uri: mongodb://8.142.10.1:27017/webFlux
username: root
password: "123456"
host: 8.142.109.15
port: 27017
database: webFlux
实体类
@Document(collection = "user")
@Data
public class User {
@Id
private String id;
@NotBlank
private String name;
@Range(min=10, max=100)
private int age;
}
数据层类
data-mongodb-reactive的操作方式类似于mybatis-plus
封装了基础的增删改查
@Repository
public interface UserRepository extends ReactiveMongoRepository<User,String> {
/**
* 根据年龄查询
* @param start
* @param end
* @return
*/
Flux<User> findByAgeBetween(int start,int end);
@Query("{'age':{ '$gte': 20, '$lte' : 30}}")
Flux<User> oldUser();
}
web类
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserRepository userRepository;
/**
* 数组形式一次性返回数据
* @return
*/
@GetMapping("getAll")
public Flux<User> getAll() {
return userRepository.findAll();
}
/**
* 以SSE形式多次返回数据
* @return
*/
@GetMapping(value = "get",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamGetAll() {
return userRepository.findAll();
}
/**
* 保持
* @param user
* @return
*/
@PostMapping("save")
public Mono<User> createUser(@Valid @RequestBody User user) {
user.setId(null);
CheckUtil.checkName(user.getName());
return userRepository.save(user);
}
/**
* 删除
* @param id
* @return
*/
@DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> deleteUser(@PathVariable("id") String id) {
return userRepository.findById(id)
//当要操作数据并返回一个Mono这个时候使用flatMap
//如果不操作数据,只传转换数据,使用map
.flatMap(user -> userRepository.delete(user))
.then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
/**
* 更改
* @param id
* @param user
* @return
*/
@PutMapping("{id}")
public Mono<ResponseEntity<User>> updateUser(@PathVariable String id,
@Valid @RequestBody User user){
return userRepository.findById(id)
//flatMap操作数据
.flatMap(u->{
u.setAge(user.getAge());
u.setName(user.getName());
return userRepository.save(u);})
//转换数据
.map(user1 -> new ResponseEntity<>(user1, HttpStatus.OK))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
/**
* 根据id查找
* @param id
* @return
*/
@GetMapping("{id}")
public Mono<ResponseEntity<User>> findUserById(@PathVariable String id){
return userRepository.findById(id)
.map(u -> new ResponseEntity<>(u, HttpStatus.OK))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
/**
* 根据年龄查询
* @param start
* @param end
* @return
*/
@GetMapping(value = "/age/{start}/{end}",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> findByAge(@PathVariable("start") int start,
@PathVariable("end") int end){
return userRepository.findByAgeBetween(start, end);
}
@GetMapping("old")
public Flux<User> findByAge(){
return userRepository.oldUser();
}
}
异常处理切面
@ControllerAdvice
public class CheckAdvice {
@ExceptionHandler(WebExchangeBindException.class)
public ResponseEntity<String> errorHandler(WebExchangeBindException e){
return new ResponseEntity<String>(toString(e),HttpStatus.BAD_REQUEST);
}
private String toString(WebExchangeBindException e) {
return e.getFieldErrors().stream()
.map(ex -> ex.getField() + ":" + ex.getDefaultMessage())
.reduce("", (s1, s2) -> s1 + "\n" + s2);
}
}
尝试一下自定义参数校验
异常类
@Data
public class checkException extends RuntimeException{
private String fieldName;
private String value;
public checkException(String fieldName, String value) {
super();
this.fieldName = fieldName;
this.value = value;
}
}
Util类
/**
* 校验名字是否有敏感词
*/
public class CheckUtil {
private static final String[] INVALID_NAME = {"admin","root"};
public static void checkName(String value) {
Stream.of(INVALID_NAME).filter(name -> name.equalsIgnoreCase(value))
.findAny().ifPresent(name->{throw new checkException(name,value);
});
}
}
切面类
类似于刚才的操作把我们自定义的参数校验添加到里面
@ExceptionHandler(checkException.class)
public ResponseEntity<String> errorCheckException(checkException e){
return new ResponseEntity<String>(toString(e),HttpStatus.BAD_REQUEST);
}
private String toString(checkException e){
return e.getFieldName() + ":错误的值是" + e.getValue();
}
踩雷
1.首先mongodb的密码是字符串需要添加双引号
2.mongodb的用户操作数据库需要有readwrite的权限
3.mongodb的实体类id的类型必须是String
4.mongodb不能直接配置uri那种是无用户登录
同时分享一个嘎嘎好用的API工具
Restlet Client
eckException(checkException e){
return new ResponseEntity(toString(e),HttpStatus.BAD_REQUEST);
}
private String toString(checkException e){
return e.getFieldName() + “:错误的值是” + e.getValue();
}
## 踩雷
1.首先mongodb的密码是字符串需要添加双引号
2.mongodb的用户操作数据库需要有readwrite的权限
3.mongodb的实体类id的类型必须是String
4.mongodb不能直接配置uri那种是无用户登录
同时分享一个嘎嘎好用的API工具
Restlet Client
![请添加图片描述](https://img-blog.csdnimg.cn/97af0dbc9a5d40a9a0c628a3848ff8b2.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP6LW15ZGi,size_20,color_FFFFFF,t_70,g_se,x_16)