响应式
spring 对响应式的支持
-
spring 5 新框架 Spring webFlux
-
电商 金融 对 严谨要求高,对于数据的一致性十分重要
-
并发:通过锁 保证一些重要数据的一致性
-
游戏 视频 新闻 广告,不需要很高的数据一致性
-
但 : 对于并发数 和 相应速度 十分在意
-
传统的模式会引入 一致性的机制,会造成性能瓶颈,所以提出了:响应式编程。
-
Servlet 3.1 之后,就支持响应式编程
-
Spring boot 1.x.x 不支持
-
Spring boot 2.x.x 才能支持
-
在讨论 WebFlux之前,需要了解 RxJava 和 Reactor
基础概念
- 响应式编程
- 是一种 面向数据流 和 变化传播 的 编程规范
- 数据流 data streams
- 异步 asynchronous
- 消息
响应式编程的宣言
- 基于相应式宣言 的理念所产生的编程方式。分为4大理念
- 灵敏的
- 可恢复的 :强大的容错机制 和 修复机制
- 可伸缩的:根据自身压力,实现伸缩
- 消息驱动:异步消息机制,事件之间的协作是通过消息进行的
- 基于这样的理念,提出了各种模型,著名的有:Reactor 和 RxJava
- Spring 5 基于 他们构建 Webflux,默认情况下 使用 Reactor
Reactor模型
-
传统的模型
- 请求1,请求2 ,到达请求队列 ——分发器
- 分发成 线程1 ,线程2
- 往往请求量,大于系统最大线程数
- 新来的线程 要等到 旧线程 运行完成后 才能提供服务,要么被系统所抛弃
- 如:视频,游戏,图片,复杂计算
-
Reactor 反应器模式
-
客户端1,客户端2 ——每个客户端生成一个event——事件都到 selector中
-
selector 分发出 n个 Dispatcher——(每个dispatcher对应一个) Request Handler1
-
上面是,看图的描述,下面是官方的解释:
- 客户端像服务器 注册 其感兴趣 的事件 ,(客户端订阅了对应的事件,不给服务器发送请求),
- 客户端发生一些已经注册的事件时,会触发服务器的相应。
- 此时 服务器存在一个 Select线程,负责轮询客户端发送过来的事件
- 找到对应的请求处理器 Request Handler,启用另一个线程运行。
- select线程只是轮询,相应非常快。
- 事件有多种,请求处理器有多个。(区分事件的类型)
- select 存在路由,
- 请求处理器处理业务,结果最终转换为 数据流 data Stream 发送到客户端
- 数据流的处理,还有背压 back pressure等。
-
-
简单解释:
- Reactor 是基于事件的模型
- 对于服务器而言,它也是一种异步的
- select线程轮询到事件,通过路由 找到处理器去 运行对应的逻辑
- 处理器 最后返回的结果 会转换为数据流
-
我的简单理解:
- 客户端 注册事件,并订阅 触发响应。
- 服务器存在 selector ,去轮询事件,找到 request handler
- request handler 启用另一个线程,结果最终转为 数据流。
Spring WebFlux的概述
-
Servlet 3.1 之前 web容器 都是基于 阻塞机制 开发的
-
高并发 网站,使用函数式 的 编程 就 更为 直观 和 简易
-
特别是 那些需要高速响应 而 对业务 逻辑 ,要求 并不十分严格的网站。
- 如:游戏,视频,新闻浏览
-
java 8 之后,引入了 Lambda 和 Functional接口
-
Spring 5 推出了 Spring WebFlux 新一代的 Web响应式 编程 框架。
-
分为:
- Router Functions ,路由分发层。在reactor中,它就是selector作用
- Spring WebFlux,控制层。处理逻辑前 封装 和 控制数据流返回格式
- HTTP/Reactive Streams 将结果转换为 数据流的过程
-
需要能够支持 Servlet 3.1 + 的容器, tomcat jetty undertow
-
异步编程,使用最多的是 netty , 在 webFlux 的starter中 默认依赖 netty
-
两种开发模式:
- 类似于 MVC的模式
- 函数功能性编程
-
数据流的封装,Reactor提供的 Flux 和 Mono
- flux 存放 0—N 个数据流 序列,响应式框架会一个接一个将它们发送到客户端
- mono 存放0-1 个数据流序列,
- 可以相互装换
-
背压 backpressure ,只对 Flux有意义
- 如果在很短的时间内,服务端将 大量的数据流 传输给 客户端,那么客户端就可能被压垮
- 为了解决这个问题,一般使用 响应式拉取,将服务端的数据流 划分为多个序列,当客户端处理完这个序列后,在给服务端发送消息,然后在拉取第二个序列
webHandler接口 和 运行流程
-
mvc 使用 DispatcherServlet
-
webFlux 使用 WebHandler
-
WebHandler接口
- DispatcherHandler 分发处理器 核心
- ResourceWebHandler 资源处理器
- WebHandlerDecorator webHandler装饰器,装饰着模式
- Exception Handling WebHandler
- Filtering WebHandler
- Http WebHandler Adapter
DispatcherHandler 源码
public Mono<Void> handle(ServerWebExchange exchange) {
if (logger.isDebugEnabled()) {
ServerHttpRequest request = exchange.getRequest();
logger.debug("Processing " + request.getMethodValue() + " request for [" + request.getURI() + "]");
}
return this.handlerMappings == null ? Mono.error(HANDLER_NOT_FOUND_EXCEPTION) :
//框架封装数据流 flux
Flux
.fromIterable(this.handlerMappings)//循环 handlerMappings
.concatMap(
(mapping) -> { //找到合适的处理器
return mapping.getHandler(exchange);
}
)
.next()//处理第一条记录
.switchIfEmpty( //如果找不到处理器的情况
Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
//通过反射运行处理器
.flatMap((handler) -> {
return this.invokeHandler(exchange, handler);
}).flatMap((result) -> {
//解析结果,将其转换 为 对应的数据流 序列
return this.handleResult(exchange, result);
});
}
-
与 Spring MVC 一样,都是从 HandlerMapping 找到对应 的处理器
-
getHandler方法找到对应的处理器
-
invokeHandler方法运行 处理器
-
找到合适 handlerAdapter去运行处理
-
handleResult 将结果 转变为 对应的数据流
-
HandlerMapping 处理映射器 (@Controller @RequestMapping 获得)
-
到达 DispatcherHandler 分发处理器
-
HandlerAdapter 处理适配器
-
Result 处理器结果
-
分发处理器
-
handleResult 结果处理,转换为对应的数据流
引入pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency> mongodb-reactive
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency> webflux
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency> tomcat
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency> 这里书上没引用
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
- 以 mongoDB 作为响应式 变成的数据源
- 引用的tomcat 作为默认的服务器
- Spring WebFlux 只能支持 Spring Data Reactive 非阻塞的数据响应方式
- 数据库的开发往往是阻塞的,所以不适用。
- Spring Data Reactive 支持 Redis , MongoDB ,NoSQL
- redis偏向 作为缓存使用,所以最广泛的用 MongoDB
- 不能引用 spring-boot-starter-web ,否则 只会加在 Spring MVC 了。
通过MVC方式 开发WebFlux服务端
pojo
public class User implements Serializable {
private static final long serialVersionUID = 3923229573077975377L;
@Id
private Long id;
// 性别
private SexEnum sex;
// 在MongoDB中使用user_name保存属性
@Field("user_name")
private String userName;
private String note;
}
public enum SexEnum {
MALE(1, "男"),
FEMALE(0, "女");
private int code;
private String name;
SexEnum(int code, String name) {
this.code = code;
this.name = name;
}
public static SexEnum getSexEnum(int code) {
SexEnum [] emuns = SexEnum.values();
for (SexEnum item : emuns) {
if (item.getCode() == code) {
return item;
}
}
return null;
}
}
dao
@Repository
//请注意这里需要继承ReactiveMongoRepository
public interface UserRepository extends ReactiveMongoRepository<User, Long> {
/**
* 对用户名和备注进行模糊查询
* @param userName —— 用户名称
* @param note —— 备注
* @return 符合条件的用户
*/
public Flux<User> findByUserNameLikeAndNoteLike(
String userName, String note);
}
- webFlux为响应式提供了接口: ReactiveMongoRepository
- find ByUserName Like And NoteLike
service
public interface UserService {
Mono<User> getUser(Long id);
Mono<User> insertUser(User user);
Mono<User> updateUser(User user);
Mono<Void> deleteUser(Long id);
Flux<User> findUsers(String userName, String note);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public Mono<User> getUser(Long id) {
return userRepository.findById(id);
}
@Override
public Mono<User> insertUser( User user) {
return userRepository.save(user);
}
@Override
public Mono<User> updateUser(User user) {
return userRepository.save(user);
}
@Override
public Mono<Void> deleteUser(Long id) {
Mono<Void> result = userRepository.deleteById(id);
return result;
}
@Override
public Flux<User> findUsers(String userName, String note) {
return userRepository.findByUserNameLikeAndNoteLike(userName, note);
}
}
action (flux 和 mono 说明)
-
也可用 @Controller @RestController @GetMapping
-
使用 REST 风格 更合适
- 定义用户的视图
public class UserVo {
private Long id;
private String userName;
private int sexCode;
private String sexName;
private String note;
}
- Flux 0—N 个数据流 序列
- Mono 0—1 个 数据流序列
- @GetMapping ,这样的请求 就会被解析到 HandlerMapping 的机制中
- 就能够 根据 URI 进行路由 到对应的方法中去
// REST风格控制器,
@RestController //返回的内容将转为json对象
public class UserController {
@Autowired
private UserService userService;
// 获取用户
@GetMapping("/user/{id}")
public Mono<UserVo> getUser(@PathVariable Long id) {
return userService.getUser(id)
// 从User对象转换为UserVo对象
.map(u -> translate(u));
}
// 新增用户
@PostMapping("/user")
public Mono<UserVo> insertUser(@RequestBody User user) {
return userService.insertUser(user)
// 从User对象转换为UserVo对象
.map(u -> translate(u));
}
// 更新用户
@PutMapping("/user")
public Mono<UserVo> updateUser(@RequestBody User user) {
return userService.updateUser(user)
// 从User对象转换为UserVo对象
.map(u -> translate(u));
}
// 删除用户
@DeleteMapping("/user/{id}")
public Mono<Void> deleteUser(@PathVariable Long id) {
return userService.deleteUser(id);
}
// 查询用户
@GetMapping("/user/{userName}/{note}")
public Flux<UserVo> findUsers(@PathVariable String userName, @PathVariable String note) {
return userService.findUsers(userName, note)
// 从User对象转换为UserVo对象
.map(u -> translate(u));
}
// 加入局部验证器
@InitBinder
public void initBinder(DataBinder binder) {
binder.setValidator(new UserValidator());
}
/***
* 完成PO到VO的转换
*
* @param user
* ——PO 持久对象
* @return UserVo ——VO 视图对象
*/
private UserVo translate(User user) {
UserVo userVo = new UserVo();
userVo.setUserName(user.getUserName());
userVo.setSexCode(user.getSex().getCode());
userVo.setSexName(user.getSex().getName());
userVo.setNote(user.getNote());
userVo.setId(user.getId());
return userVo;
}
}
配置文件
# MongoDB服务器
spring.data.mongodb.host=192.168.10.128
# MongoDB用户名。docker默认启动 没有用户名和密码
spring.data.mongodb.username=spring
# MongoDB密码
spring.data.mongodb.password=123456
# MongoDB端口
spring.data.mongodb.port=27017
# MongoDB库名称
spring.data.mongodb.database=springboot
spring.webflux.static-path-pattern=/static/**
// 定义扫描包
@SpringBootApplication(scanBasePackages="com.springboot.chapter14")
// 由于引入JPA,默认的情况下,需要配置数据源,
// 通过@EnableAutoConfiguration排除原有自动配置的数据源
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
// 在WebFlux下,驱动MongoDB的JPA接口
@EnableReactiveMongoRepositories(
// 定义扫描的包
basePackages="com.springboot.chapter14.repository")
public class Chapter14Application {
public static void main(String[] args) {
SpringApplication.run(Chapter14Application.class, args);
}
}
- 默认会 尝试 装配 关系数据库 数据源 ,所以排除数据源初始化
- EnableReactiveMongoRepositories 开启响应式 MongoDB JPA接口
- http://localhost:8080/user/1
客户端开发,WebClient
-
用户请求
-
网关
- 产品微服务:发布管理产品
- 用户微服务:管理会员
- 账户微服务:管理财务
- 交易微服务:
-
一次交易:用户微服务 判断用户等级,财务微服务管理用户的消费款项,产品微服务管理产品的发放,交易微服务记录交易的发生情况。
-
为了方便微服务之间的调用,webFlux 提供了 WebClient类,比RestTemplate更强大
public class Chapter14WebClient {
public static void main(String[] args) {
// 创建WebClient对象,并且设置请求基础路径
WebClient client = WebClient.create("http://localhost:8080");
getSecurityUser(client, 1L);
updateUserName(client, 1L, "update_user_name");
getUserPojo(client, 1L);
// 一个新的用户
User newUser = new User();
newUser.setId(6L);
newUser.setNote("note_6");
newUser.setUserName("user_name_6");
newUser.setSex(SexEnum.MALE);
insertUser3(client, newUser);
insertUser2(client);
// 一个新的用户
User newUser = new User();
newUser.setId(1L);
newUser.setNote("note_1");
newUser.setUserName("user_name_1");
newUser.setSex(SexEnum.MALE);
// 新增用户
insertUser(client, newUser);
// 获取用户
getUser(client, 1L);
User updUser = new User();
updUser.setId(1L);
updUser.setNote("note_update");
updUser.setUserName("user_name_update");
updUser.setSex(SexEnum.FEMALE);
// 更新用户
updateUser(client, updUser);
// 查询用户
findUsers(client, "user", "note");
// 删除用户
deleteUser(client, 3L);
}
private static void getUser(WebClient client, Long id) {
Mono<UserVo> userMono =
// 定义GET请求
client.get()
// 定义请求URI和参数
.uri("/user/{id}", id)
// 接收请求结果类型
.accept(MediaType.APPLICATION_STREAM_JSON)
// 设置请求结果检索规则
.retrieve()
// 将结果体转换为一个Mono封装的数据流
.bodyToMono(UserVo.class);
// 获取服务器发布的数据流,此时才会发送请求
UserVo user = userMono.block();
System.out.println("【用户名称】" + user.getUserName());
}
//get() .uri .accept(MediaType.APPLICATION_STREAM_JSON)
//.retrieve() .bodyToMono(UserVo.class);
private static void insertUser(WebClient client, User newUser) {
// 注意这只是定义一个时间,并不会发送请求
Mono<UserVo> userMono =
// 定义POST请求
client.post()
// 设置请求URI
.uri("/user")
// 请求体为JSON数据流
.contentType(MediaType.APPLICATION_STREAM_JSON)
// 请求体内容
.body(Mono.just(newUser), User.class)
// 接收请求结果类型
.accept(MediaType.APPLICATION_STREAM_JSON)
// 设置请求结果检索规则
.retrieve()
// 将结果体转换为一个Mono封装的数据流
.bodyToMono(UserVo.class);
// 获取服务器发布的数据流,此时才会发送请求
UserVo user = userMono.block();
System.out.println("【用户名称】" + user.getUserName());
}
// post() .uri("/user") .contentType .body(Mono.just(newUser), User.class)
// .accept .retrieve() .bodyToMono
//userMono.block(); 才会发送请求
private static void insertUser3(WebClient client, User newUser) {
// 注意这只是定义一个时间,并不会发送请求
Mono<UserVo> userMono =
// 定义POST请求
client.post()
// 设置请求URI
.uri("/user3")
// 请求体为JSON数据流
.contentType(MediaType.APPLICATION_STREAM_JSON)
// 请求体内容
.body(Mono.just(newUser), User.class)
// 接收请求结果类型
.accept(MediaType.APPLICATION_STREAM_JSON)
// 设置请求结果检索规则
.retrieve()
// 将结果体转换为一个Mono封装的数据流
.bodyToMono(UserVo.class);
// 获取服务器发布的数据流,此时才会发送请求
UserVo user = userMono.block();
System.out.println("【用户名称】" + user.getUserName());
}
private static void updateUser(WebClient client, User updUser) {
Mono<UserVo> userMono =
// 定义PUT请求
client.put().uri("/user")
// 请求体为JSON数据流
.contentType(MediaType.APPLICATION_STREAM_JSON)
// 请求体内容
.body(Mono.just(updUser), User.class)
// 接收请求结果类型
.accept(MediaType.APPLICATION_STREAM_JSON)
// 设置请求结果检索规则
.retrieve()
// 将结果体转换为一个Mono封装的数据流
.bodyToMono(UserVo.class);
// 获取服务器发布的数据流,此时才会发送请求
UserVo user = userMono.block();
System.out.println("【用户名称】" + user.getUserName());
}
private static void findUsers(WebClient client, String userName, String note) {
// 定义参数map
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userName", userName);
paramMap.put("note", note);
Flux<UserVo> userFlux =
// 定义PUT请求,使用Map传递多个参数
client.get().uri("/user/{userName}/{note}", paramMap)
// 接收请求结果类型
.accept(MediaType.APPLICATION_STREAM_JSON)
// 设置请求结果检索规则
.retrieve()
// 将结果体转换为一个Mono封装的数据流
.bodyToFlux(UserVo.class);
// 通过Iterator遍历结果数据流,执行后服务器才会响应
Iterator<UserVo> iterator = userFlux.toIterable().iterator();
// 遍历
while (iterator.hasNext()) {
UserVo item = iterator.next();
System.out.println("【用户名称】" + item.getUserName());
}
}
//.bodyToFlux
// userFlux.toIterable().iterator() 才会发送请求
//下来式处理,只在每一次 执行循环 时,才会向服务器 要一个 数据流序列 到 客户端处理。
//当处理完一个数据流序列后,才会执行第二次,获取下一个数据流序列。直到获取所有的数据流序列。
private static void deleteUser(WebClient client, Long id) {
Mono<Void> result = client.delete()
// 设置请求URI
.uri("/user/{id}", id)
// 接收请求结果类型
.accept(MediaType.APPLICATION_STREAM_JSON)
// 设置请求结果检索规则
.retrieve()
// 将结果体转换为一个Mono封装的数据流
.bodyToMono(Void.class);
// 获取服务器发布的数据流,此时才会发送请求
Void voidResult = result.block();
System.out.println(voidResult);
}
//.bodyToMono(Void.class);
private static void insertUser2(WebClient client) {
// 注意这只是定义一个时间,并不会发送请求
Mono<UserVo> userMono =
// 定义POST请求
client.post()
// 设置请求URI,和约定格式的用户信息
.uri("/user2/{user}", "2-convert4-0-note4")
// 接收请求结果类型
.accept(MediaType.APPLICATION_STREAM_JSON)
// 设置请求结果检索规则
.retrieve()
// 将结果体转换为一个Mono封装的数据流
.bodyToMono(UserVo.class);
// 获取服务器发布的数据流,此时才会发送请求
UserVo user = userMono.block();
System.out.println("【用户名称】" + user.getUserName());
}
// 转换方法
private static UserPojo translate(UserVo vo) {
if (vo == null) {
return null;
}
UserPojo pojo = new UserPojo();
pojo.setId(vo.getId());
pojo.setUserName(vo.getUserName());
// 性别转换
pojo.setSex(vo.getSexCode() == 1 ? 1 : 2);
pojo.setNote(vo.getNote());
return pojo;
}
public static void getUserPojo(WebClient client, Long id) {
Mono<UserPojo> userMono =
// HTTP GET请求
client.get()
// 定义请求URI和参数
.uri("/user/{id}", id)
// 接收结果为JSON数据流
.accept(MediaType.APPLICATION_STREAM_JSON)
// 启用交换
.exchange()
// 出现错误则返回空
.doOnError(ex -> Mono.empty())
// 获取服务器发送过来的UserVo对象
.flatMap(response -> response.bodyToMono(UserVo.class))
// 通过自定义方法转换为客户端的UserPojo
.map(user -> translate(user));
// 获取客户端的UserPojo
UserPojo pojo = userMono.block();
// 不为空打印信息
if (pojo != null) {
System.out.println("获取的用户名称为" + pojo.getUserName());
} else {
System.out.println("获取的用户编号为" + id + "失败");
}
}
}
深入了解 WebFlux服务端开发
-
读取请求的http首部所设置内容
-
新增参数转换器 和 验证规则
-
错误的处理
-
实现 WebFluxConfigurer
类型转换器
配置
- 约定用户 以字符串格式: {id}-{useerName}-{sex}-{note} 传递
// 实现Java 8的接口WebFluxConfigurer,该接口都是default方法
@Configuration
public class WebFluxConfig implements WebFluxConfigurer {
// 注册Converter
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(stringToUserConverter());
}
// 定义String --> User类型转换器
// @Bean// 如果定义为Spring Bean,Spring Boot会自动识别为类型转换器
// 这种方法 更加简单
public Converter<String, User> stringToUserConverter() {
Converter<String, User> converter = new Converter<String, User>() {
@Override
public User convert(String src) {
String strArr[] = src.split("-");
User user = new User();
Long id = Long.valueOf(strArr[0]);
user.setId(id);
user.setUserName(strArr[1]);
int sexCode = Integer.valueOf(strArr[2]);
SexEnum sex = SexEnum.getSexEnum(sexCode);
user.setSex(sex);
user.setNote(strArr[3]);
return user;
}
};
return converter;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
// 注册资源,可以通过URI访问
.addResourceHandler("/resources/static/**")
// 注册Spring资源,可以在Spring机制中访问
.addResourceLocations("/public/**", "classpath:/static/")
// 缓存一年(365天)
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
// // 设置全局性验证器
// @Override
// public Validator getValidator() {
// return new UserValidator();
// }
}
使用
- 使用转换器
@PostMapping("/user2/{user}")
public Mono<UserVo> insertUser2(@PathVariable("user") User user) {
return userService.insertUser(user)
// 进行PO和VO之间的转换
.map(u -> translate(u));
}
webClient测试
private static void insertUser2(WebClient client) {
// 注意这只是定义一个时间,并不会发送请求
Mono<UserVo> userMono =
// 定义POST请求
client.post()
// 设置请求URI,和约定格式的用户信息
.uri("/user2/{user}", "2-convert4-0-note4")
// 接收请求结果类型
.accept(MediaType.APPLICATION_STREAM_JSON)
// 设置请求结果检索规则
.retrieve()
// 将结果体转换为一个Mono封装的数据流
.bodyToMono(UserVo.class);
// 获取服务器发布的数据流,此时才会发送请求
UserVo user = userMono.block();
System.out.println("【用户名称】" + user.getUserName());
}
- 日期的格式化:spring.webflux.date-format=yyyy-MM-dd
验证器
定义
public class UserValidator implements Validator {
// 确定支持的验证类型
@Override
public boolean supports(Class<?> clazz) {
return clazz.equals(User.class);
}
// 验证逻辑
@Override
public void validate(Object target, Errors errors) {
User user = (User) target;
// 监测用户名是否为空
if (StringUtils.isEmpty(user.getUserName())) {
errors.rejectValue("userName", null, "用户名不能为空");
}
}
}
配置
@Configuration
public class WebFluxConfig implements WebFluxConfigurer {
// 设置全局性验证器
@Override
public Validator getValidator() {
return new UserValidator();
}
}
- 创建一个验证器,不能是多个,为各个控制器所共享
使用
@PostMapping("/user3")
public Mono<UserVo> insertUser3(@Valid @RequestBody User user) {
return userService.insertUser(user)
// 进行PO和VO之间的转换
.map(u -> translate(u));
}
- 这里加入的是 全局验证器,有时候希望 局部验证器
- 可以仿照 MVC的 @InitBinder,将类和 验证器进行绑定
//只会在这一个controller生效
// 加入局部验证器
@InitBinder
public void initBinder(DataBinder binder) {
binder.setValidator(new UserValidator());
}
访问静态资源
@Configuration
public class WebFluxConfig implements WebFluxConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
// 注册资源,可以通过URI访问
.addResourceHandler("/resources/static/**")
// 注册Spring资源,可以在Spring机制中访问
.addResourceLocations("/public/**", "classpath:/static/")
// 缓存一年(365天)
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
}
- 直接通过URI访问 static下资源
深入客户端开发
-
设置请求头
-
服务端发生了异常处理
-
之前是通过 retrieve 方法 将 服务端的数据流转换
错误处理
public static void getUser2(WebClient client, Long id) {
Mono<UserVo> userMono =
// HTTP GET请求
client.get()
// 定义请求URI和参数
.uri("/user/{id}", id)
// 接收结果为JSON数据流
.accept(MediaType.APPLICATION_STREAM_JSON)
// 设置检索
.retrieve().onStatus(
// 发生4开头或者5开头的状态码,4开头是客户端错误,5开头是服务器错误
// 第一个Lambda表达式,返回如果为true,则执行第二个Lambda表达式
status -> status.is4xxClientError() || status.is5xxServerError(),
// 如果发生异常,则用第二个表达式返回作为结果
// 第二个Lambda表达式
response -> Mono.empty())
// 将请求结果转换为Mono数据流
.bodyToMono(UserVo.class);
UserVo user = userMono.block();
// 如果用户正常返回
if (user != null) {
System.out.println("【用户名称】" + user.getUserName());
} else {// 不能正常返回或者用户为空
System.out.println("服务器没有返回编号为:" + id + "的用户");
}
}
- response -> Mono.empty()) 让结果转变成空
自定义转换规则
//客户端的pojo
public class UserPojo {
private Long id;
private String userName;
// 1-男 2-女
private int sex = 1;
private String note = null;
}
//服务器的pojo
public class UserVo {
private Long id;
private String userName;
private int sexCode;
private String sexName;
private String note;
}
// 转换方法
private static UserPojo translate(UserVo vo) {
if (vo == null) {
return null;
}
UserPojo pojo = new UserPojo();
pojo.setId(vo.getId());
pojo.setUserName(vo.getUserName());
// 性别转换
pojo.setSex(vo.getSexCode() == 1 ? 1 : 2);
pojo.setNote(vo.getNote());
return pojo;
}
public static void getUserPojo(WebClient client, Long id) {
Mono<UserPojo> userMono =
// HTTP GET请求
client.get()
// 定义请求URI和参数
.uri("/user/{id}", id)
// 接收结果为JSON数据流
.accept(MediaType.APPLICATION_STREAM_JSON)
// 启用交换
.exchange()
// 出现错误则返回空
.doOnError(ex -> Mono.empty())
// 获取服务器发送过来的UserVo对象
.flatMap(response -> response.bodyToMono(UserVo.class))
// 通过自定义方法转换为客户端的UserPojo
.map(user -> translate(user));
// 获取客户端的UserPojo
UserPojo pojo = userMono.block();
// 不为空打印信息
if (pojo != null) {
System.out.println("获取的用户名称为" + pojo.getUserName());
} else {
System.out.println("获取的用户编号为" + id + "失败");
}
}
-
translate(user) 将 服务端对象 转换为 客户端对象
-
不用 retrieve而是:.exchange() 方法,允许自定义转换
-
.flatMap(response -> response.bodyToMono(UserVo.class)) 对服务器的方法请求 转换为 Mono对象
-
.map(user -> translate(user)); 转成 Mono
设置请求头
@PutMapping("/user/name")
public Mono<UserVo> updateUserName(@RequestHeader("id") Long id,
@RequestHeader("userName") String userName) {
Mono<User> userMono = userService.getUser(id);
User user = userMono.block();
if (user == null) { // 查找不到用户信息,抛出运行异常消息......
throw new RuntimeException("找不到用户信息");
}
user.setUserName(userName);
return this.updateUser(user);
}
//这里没用
public static void updateUserName(WebClient client, Long id, String userName) {
Mono<UserVo> monoUserVo = client
// HTTP PUT请求
.put()
// 请求URI
.uri("/user/name", userName)
// 第一个请求头
.header("id", id +"")
// 第二个请求头
.header("userName", userName)
// 设置接收JSON数据流
.accept(MediaType.APPLICATION_STREAM_JSON)
// 检索
.retrieve()
// 根据服务端响应码处理逻辑
.onStatus(
status -> status.is4xxClientError() || status.is5xxServerError(),
response -> Mono.empty())
// 转换为UserVo对象
.bodyToMono(UserVo.class);
UserVo userVo = monoUserVo.block();
// 不为空打印信息
if (userVo != null) {
System.out.println("获取的用户名称为" + userVo.getUserName());
} else {
System.out.println("获取的用户编号为" + id + "失败");
}
}
public static void getSecurityUser(WebClient client, Long id) {
Mono<UserVo> monoUserVo = client
// HTTP PUT请求
.get()
// 请求URI
.uri("/security/user/{id}", id)
// 第一个请求头
.header("header_password", "pwd")
// 第二个请求头
.header("header_user", "user")
// 设置接收JSON数据流
.accept(MediaType.APPLICATION_STREAM_JSON)
// 检索
.retrieve()
// 根据服务端响应码处理逻辑
.onStatus(
status -> status.is4xxClientError() || status.is5xxServerError(),
response -> Mono.empty())
// 转换为UserVo对象
.bodyToMono(UserVo.class);
UserVo userVo = monoUserVo.block();
// 不为空打印信息
if (userVo != null) {
System.out.println("获取的用户名称为" + userVo.getUserName());
} else {
System.out.println("获取的用户编号为" + id + "失败");
}
}
使用路由函数方式 开发 WebFlux
- 路由函数 router functions
- 体验了高并发的特性,函数式编程 的潮流
- 可读性,可维护性变差
处理器
- 需要开发一个处理器,用来处理各种场景
- 在这个基础上,可开发用户处理器
@Service
public class UserHandler {
@Autowired
private UserRepository userRepository = null;
public Mono<ServerResponse> getUser(ServerRequest request) {
// 获取请求URI参数
String idStr = request.pathVariable("id");
Long id = Long.valueOf(idStr);
Mono<UserVo> userVoMono = userRepository.findById(id)
// 转换为UserVo
.map(u -> translate(u));
return ServerResponse
// 响应成功
.ok()
// 响应体类型
.contentType(MediaType.APPLICATION_JSON_UTF8)
// 响应体
.body(userVoMono, UserVo.class);
}
// .cache() 不使用:程序就会在等待数据的接收
public Mono<ServerResponse> insertUser(ServerRequest request) {
Mono<User> userMonoParam = request.bodyToMono(User.class);
Mono<UserVo> userVoMono = userMonoParam
// 缓存请求体
.cache()
// 处理业务逻辑,转变数据流
.flatMap(user ->userRepository.save(user)
// 转换为UserVo对象
.map(u->translate(u)));
return ServerResponse
// 响应成功
.ok()
// 响应体类型
.contentType(MediaType.APPLICATION_JSON_UTF8)
// 响应体
.body(userVoMono, UserVo.class);
}
public Mono<ServerResponse> updateUser(ServerRequest request) {
Mono<User> userMonoParam = request.bodyToMono(User.class);
Mono<UserVo> userVoMono = userMonoParam.cache()
.flatMap(user ->userRepository.save(user)
.map(u->translate(u)));
return ServerResponse
// 响应成功
.ok()
// 响应体类型
.contentType(MediaType.APPLICATION_JSON_UTF8)
// 响应体
.body(userVoMono, UserVo.class);
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
// 获取请求URI参数
String idStr = request.pathVariable("id");
Long id = Long.valueOf(idStr);
Mono<Void> monoVoid = userRepository.deleteById(id);
return ServerResponse
// 响应成功
.ok()
// 响应体类型
.contentType(MediaType.APPLICATION_JSON_UTF8)
// 响应体
.body(monoVoid, Void.class);
}
public Mono<ServerResponse> findUsers(ServerRequest request) {
String userName = request.pathVariable("userName");
String note = request.pathVariable("note");
Flux<UserVo> userVoFlux =
userRepository.findByUserNameLikeAndNoteLike(userName, note)
.map(u -> translate(u));
// 请参考getUser方法的注释
return ServerResponse
.ok()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(userVoFlux, UserVo.class);
}
public Mono<ServerResponse> updateUserName(ServerRequest request) {
// 获取请求头数据
String idStr = request.headers().header("id").get(0);
Long id = Long.valueOf(idStr);
String userName = request.headers().header("userName").get(0);
// 获取原有用户信息
Mono<User> userMono = userRepository.findById(id);
User user = userMono.block();
// 修改用户名
user.setUserName(userName);
Mono<UserVo> result = userRepository.save(user).map(u -> translate(u));
// 响应结果
return ServerResponse
.ok()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(result, UserVo.class);
}
/***
* 完成PO到VO的转换
*
* @param user
* PO 持久对象
* @return UserVo ——VO 视图对象
*/
private UserVo translate(User user) {
UserVo userVo = new UserVo();
userVo.setUserName(user.getUserName());
userVo.setSexCode(user.getSex().getCode());
userVo.setSexName(user.getSex().getName());
userVo.setNote(user.getNote());
userVo.setId(user.getId());
return userVo;
}
}
- Mono updateUserName(ServerRequest request)
开发请求路由
-
与 请求URI 对应起来。
-
http请求 映射到方法上
//静态导入
import static org.springframework.http.MediaType.APPLICATION_STREAM_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.PUT;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@Configuration
public class RouterConfig {
// 注入用户处理器
@Autowired
private UserHandler userHandler = null;
// 用户路由
@Bean
public RouterFunction<ServerResponse> userRouter() {
RouterFunction<ServerResponse> router =
// 对应请求URI的对应关系
route(
// GET请求和其路径
GET("/router/user/{id}")
// 响应结果为JSON数据流
.and(accept(APPLICATION_STREAM_JSON)),
// 定义处理方法
userHandler::getUser)
// 增加一个路由
.andRoute(
// GET请求和其路径
GET("/router/user/{userName}/{note}").and(accept(APPLICATION_STREAM_JSON)),
// 定义处理方法
userHandler::findUsers)
// 增加一个路由
.andRoute(
// POST请求和其路径
POST("/router/user")
// 请求体为JSON数据流
.and(contentType(APPLICATION_STREAM_JSON)
// 响应结果为JSON数据流
.and(accept(APPLICATION_STREAM_JSON))),
// 定义处理方法
userHandler::insertUser)
// 增加一个路由
.andRoute(
// PUT请求和其路径
PUT("/router/user")
// 请求体为JSON数据流
.and(contentType(APPLICATION_STREAM_JSON))
// 响应结果为JSON数据流
.and(accept(APPLICATION_STREAM_JSON)),
// 定义处理方法
userHandler::updateUser)
.andRoute(
// DELETE请求和其路径
DELETE("/router/user/{id}")
// 响应结果为JSON数据流
.and(accept(APPLICATION_STREAM_JSON)),
// 定义处理方法
userHandler::deleteUser)
.andRoute(
// PUT请求和其路径
PUT("/router/user/name")
// 响应结果为JSON数据流
.and(accept(APPLICATION_STREAM_JSON)),
// 定义处理方法
userHandler::updateUserName);
return router;
}
}
使用过滤器
-
通过验证身份后,才能处理 业务逻辑
-
请求头上存放用户名和密码,才能访问
-
在上个类上加入:
// 请求头用户名属性名称 private static final String HEADER_NAME = "header_user"; // 请求头密码属性名称 private static final String HEADER_VALUE = "header_password"; @Bean public RouterFunction<ServerResponse> securityRouter() { RouterFunction<ServerResponse> router = // 对应请求URI的对应关系 route( // GET请求和其路径 GET("/security/user/{id}") // 响应结果为JSON数据流 .and(accept(APPLICATION_STREAM_JSON)), // 定义处理方法 userHandler::getUser) // 使用过滤器 .filter((request, next) -> filterLogic(request, next)); return router; } // 请求过滤器逻辑 private Mono<ServerResponse> filterLogic(ServerRequest request, HandlerFunction<ServerResponse> next) { // 取出请求头 String userName = request.headers().header(HEADER_NAME).get(0); String password = request.headers().header(HEADER_VALUE).get(0); // 验证通过的条件 if (!StringUtils.isEmpty(userName) && !StringUtils.isEmpty(password) && !userName.equals(password)) { // 接受请求 return next.handle(request); } // 请求头不匹配,则不允许请求,返回为未签名错误 401, return ServerResponse.status(HttpStatus.UNAUTHORIZED).build(); }
-
.filter((request, next) -> filterLogic(request, next));