在之前的博客,我向大家简单展示了 WebFlux 框架的简单使用,算是有了一个基本的认识,上一次的案例我们使用的是基于注解开发的模式,而其实 WebFlux 还有另外一种写法,基于功能性的模式。其实两种方式都比较接近,下面我们通过编写代码来感受一下。
首先,创建一个简单的 spring boot 项目,导入 WebFlux 依赖以及 为了方便导入 Lombok 依赖,如下:
org.springframework.boot spring-boot-starter-webflux org.projectlombok lombok
回想一下,我们在注解开发模式下,接下来就是要编写 controller 代码,而在功能性模式下,我们编写的是 handler 代码,为了方便,我们把 handler 类注入到容器中,交由 Spring 去管理,如下:
@Component
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
// 根据id查询用户信息
public Mono getUserById(ServerRequest request) {
// 获取路径中的 id 值
Integer id = Integer.valueOf(request.pathVariable("id"));
return ServerResponse
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userService.getUserById(id), User.class);
}
// 获取所有用户信息
public Mono getUsers(ServerRequest request) {
Flux users = userService.getUsers();
return ServerResponse
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(users, User.class);
}
// 新增用户信息
public Mono saveUser(ServerRequest request) {
Mono userMono = request.bodyToMono(User.class);
return userMono.flatMap(user -> {
// 校验名称
CheckUtils.checkName(user.getUsername());
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userService.saveUser(userMono), User.class);
});
}
}
注意代码中 CheckUtils.checkName(user.getUsername()); 这一句是我编写的校验代码
接下来就是编写对应的路径映射了,我们直接编写一个配置类,向容器中注入一个 RouterFunction 即可,代码如下:
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
@Configuration
public class WebFluxConfig {
@Bean
public RouterFunction routerFunction(UserHandler userHandler) {
return RouterFunctions.route(
GET("/user/{id}")
.and(accept(MediaType.APPLICATION_JSON))
, userHandler::getUserById // 调用 handler 中的方法
).andRoute(
GET("/user")
.and(accept(MediaType.APPLICATION_JSON))
, userHandler::getUsers
).andRoute(
POST("/saveuser")
, userHandler::saveUser
);
}
}
最后与注解开发案例类似,编写 service 服务类代码,如下:
public interface UserService {
public Mono getUserById(Integer id);
public Flux getUsers();
public Mono saveUser(Mono userMono);
}
@Service
public class UserServiceImpl implements UserService {
private final static Map USER_MAP = new HashMap<>();
public UserServiceImpl() {
USER_MAP.put(1, new User("lisi", "12345", "12121@qq.com", 20));
USER_MAP.put(2, new User("lisi2", "12345", "12121@qq.com", 20));
USER_MAP.put(3, new User("lisi3", "12345", "12121@qq.com", 20));
USER_MAP.put(4, new User("lisi4", "12345", "12121@qq.com", 20));
USER_MAP.put(5, new User("lisi5", "12345", "12121@qq.com", 20));
}
@Override
public Mono getUserById(Integer id) {
return Mono.justOrEmpty(USER_MAP.get(id));
}
@Override
public Flux getUsers() {
return Flux.fromIterable(USER_MAP.values());
}
@Override
public Mono saveUser(Mono userMono) {
return userMono.doOnNext(user -> {
int id = USER_MAP.size() + 1;
USER_MAP.put(id, user);
}).thenEmpty(Mono.empty());
}
}
到这里,其实我们的代码已经编写完成了,回到前面的代码,我编写了一个校验的代码,我们来完成一下,首先准备一个用于校验的自定义异常类和工具类:
@Data
public class CheckException extends RuntimeException {
private static final long serialVersionUID = 3899025507910942091L;
private String filedName;
private String filedValue;
public CheckException() {
super();
}
public CheckException(String message) {
super(message);
}
public CheckException(String message, Throwable cause) {
super(message, cause);
}
public CheckException(Throwable cause) {
super(cause);
}
public CheckException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public CheckException(String filedName, String filedValue) {
this.filedName = filedName;
this.filedValue = filedValue;
}
}
public class CheckUtils {
private static final String[] INVALID_NAME = {"admin","guanliyuan"};
public static void checkName(String value) {
Stream.of(INVALID_NAME)
.filter(name -> name.equalsIgnoreCase(value))
.findAny()
.ifPresent(name -> {
// 如果 value 与 INVALID_NAME 的值相等,抛出自定义的异常
throw new CheckException("name", value);
});
}
}
最后,我们需要编写一个用于处理异常的 handler 去实现 ErrorWebExceptionHandler 接口,如下:
@Component
@Order(-2) // 调整优先级
public class ExceptionHandler implements ErrorWebExceptionHandler {
@Override
public Mono handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
// 设置响应编码 400
response.setStatusCode(HttpStatus.BAD_REQUEST);
// 设置返回类型
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
// 得到异常信息
String exMessage = toStr(ex);
DataBuffer wrap = response.bufferFactory().wrap(exMessage.getBytes());
return response.writeWith(Mono.just(wrap));
}
private String toStr(Throwable ex) {
// 已知异常
if(ex instanceof CheckException) {
CheckException exception = (CheckException) ex;
return exception.getFiledName() + ": invalid value " + exception.getFiledValue();
}
// 未知异常
else {
ex.printStackTrace();
return ex.toString();
}
}
}
这里需要注意的是一定要加上 @Order(-2),因为默认的错误处理,在 Spring Boot 官方提供了一个 ErrorExceptionHandler,它的等级为 -1,还有需要注意的是实现的类是 ErrorExceptionHandler而不是 WebExceptionHandler,这里一定要注意,否则会出现无论等级设置为多少都不会响应自定义的 handler 的情况。从下面的代码我们也可以看到,默认的 ErrorExceptionHandler 上面有一个 @ConditionalOnMissingBean 也就是说对我们没有这个 bean 在容器中时下面的才会生效,否则使用的是我们自定义的。
这样,我们的代码才算是真正的完成了,然后就是进行测试了,我使用的是 postman 工具进行测试。



