Spring WebFlux 框架 - RouterFunction 案例

Spring WebFlux 框架 - RouterFunction 案例,第1张

Spring WebFlux 框架 - RouterFunction 案例

在之前的博客,我向大家简单展示了 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 工具进行测试。



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5703358.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存