基于Spring Webflux 搭建基本代码框架2

本篇文章将继续按照上篇文章基于Spring Webflux 搭建基本代码框架1,完成RouterFunction模式的改造方式。

一、主要改造点

在传统Controller模式下的代码基础上进一步做如下改造:

1、取消Controller入口层,相关入口路径定义转换为router层;

2、取消service业务逻辑层,相关业务逻辑统一收归到handler层,且入参统一为ServerRequest,出参统一为ServerResponse;

3、参数校验等逻辑需要单独自行代码实现;

4、全局异常处理逻辑调整为通过WebExceptionHandler实现;

改造后的代码结构如下:

image-20211008192700135

二、代码实现

(一)Controller入口层改造为router

基于函数式编程模式,应用所有入口路径定义全部在AppRouter配置类中进行定义:

import com.abinge.boot.staging.handler.StudentHandler;
import com.abinge.boot.staging.handler.TeacherHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.*;

@Configuration
public class AppRouter {
    @Bean
    RouterFunction<ServerResponse> studentRouter(StudentHandler studentHandler){
        // 此处就相当于原来StudentController上面的@RequestMapping("/student")
        return nest(path("/student"),
                    // 下面就相当于是原来StudentController类中的各个方法上的@RequestMapping("/add")
                    route(POST("/add").and(accept(MediaType.APPLICATION_JSON)), studentHandler::add)
                    .andRoute(POST("/update").and(accept(MediaType.APPLICATION_JSON)), studentHandler::add)
                    .andRoute(POST("/delete/{id}"), studentHandler::delete)
                    .andRoute(GET("/queryById/{id}"), studentHandler::queryById)
                    .andRoute(GET("/queryAll"), studentHandler::queryAll)
                );
    }

    @Bean
    RouterFunction<ServerResponse> teacherRouter(TeacherHandler teacherHandler){
    	// 此处就相当于原来TeacherController上面的@RequestMapping("/teacher")
        return nest(path("/teacher"),
        			// 下面就相当于是原来TeacherController类中的各个方法上的@RequestMapping("/add")
                    route(POST("/add").and(accept(MediaType.APPLICATION_JSON)), teacherHandler::add)
                    .andRoute(POST("/update").and(accept(MediaType.APPLICATION_JSON)), teacherHandler::add)
                    .andRoute(POST("/delete").and(accept(MediaType.APPLICATION_JSON)), teacherHandler::delete)
                    .andRoute(POST("/queryById").and(accept(MediaType.APPLICATION_JSON)), teacherHandler::queryById)
                    .andRoute(GET("/queryAll"), teacherHandler::queryAll)
                );
    }
}

(二)service业务逻辑层改造为handler

原有service业务逻辑层的代码以及原有controller层的统一响应数据格式转换逻辑全部调整到handler中进行处理;
handler的特点是入参统一为ServerRequest,出参统一为ServerResponse。

  1. StudentHandler:
import com.abinge.boot.staging.model.Result;
import com.abinge.boot.staging.model.Student;
import com.abinge.boot.staging.repository.StudentRepository;
import com.abinge.boot.staging.util.ParamCheckUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import static org.springframework.web.reactive.function.server.ServerResponse.*;

@Component
public class StudentHandler {
    @Autowired
    private StudentRepository studentRepository;

    public Mono<ServerResponse> add(ServerRequest serverRequest) {
        Mono<Student> student = serverRequest.bodyToMono(Student.class);
        return student.flatMap(stu -> {
            // 自定义的参数校验
            ParamCheckUtil.checkSaveStudent(stu);

            Mono<Result> resultMono = studentRepository.saveAll(student)
            .collectList().flatMap(stuList -> Mono.just(Result.success(stuList.get(0))));
            
            return ok().contentType(MediaType.APPLICATION_JSON)
                    .body(resultMono, Result.class);
        });
        
    }

    public Mono<ServerResponse> delete(ServerRequest serverRequest) {
    	// 1. 获取路径中的id
        String idStr = serverRequest.pathVariable("id");
        Long id = Long.valueOf(idStr);
        // 2. 首先查询出来在数据库中的数据
        return studentRepository.findById(id)
        		// 2.1 数据存在,则执行后续删除操作
                .flatMap(inDb ->
                    studentRepository.deleteById(id)
                    // 删除成功,则将删除前的数据转换为统一响应数据结构返回
                    .then(ok().contentType(MediaType.APPLICATION_JSON).body(Mono.just(Result.success(inDb)),Result.class)))
                // 2.2 数据不存在,则直接返回统一响应数据错误格式返回
                .switchIfEmpty(ok().contentType(MediaType.APPLICATION_JSON).body(Mono.just(Result.fail("未找到对应的数据")), Result.class));
    }

    public Mono<ServerResponse> queryById(ServerRequest serverRequest) {
        String id = serverRequest.pathVariable("id");
        return ok().contentType(MediaType.APPLICATION_JSON)
        		// 执行成功后,转换为统一响应数据结构返回
                .body(studentRepository.findById(Mono.just(Long.valueOf(id)))
                	.flatMap(stu -> Mono.just(Result.success(stu))), Result.class);
    }

    public Mono<ServerResponse> queryAll(ServerRequest serverRequest) {
        return ok().contentType(MediaType.APPLICATION_JSON)
        		// 执行成功后,转换为统一响应数据结构返回
                .body(studentRepository.findAll()
                	.collectList().flatMap(stuList -> Mono.just(Result.success(stuList))), Result.class);
    }

}
  1. TeacherHandler:
import com.abinge.boot.staging.model.Result;
import com.abinge.boot.staging.model.Teacher;
import com.abinge.boot.staging.repository.TeacherRepository;
import com.abinge.boot.staging.util.ParamCheckUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import static org.springframework.web.reactive.function.server.ServerResponse.ok;

@Component
public class TeacherHandler {
    @Autowired
    private TeacherRepository TeacherRepository;

    public Mono<ServerResponse> add(ServerRequest serverRequest) {
        Mono<Teacher> Teacher = serverRequest.bodyToMono(Teacher.class);
        return Teacher.flatMap(tea -> {
            // 参数校验
            ParamCheckUtil.checkSaveTeacher(tea);
            
            Mono<Result> resultMono = TeacherRepository.saveAll(Teacher)
            .collectList().flatMap(teaList -> Mono.just(Result.success(teaList.get(0))));
            
            return ok().contentType(MediaType.APPLICATION_JSON)
                    .body(resultMono, Result.class);
        });

    }

    public Mono<ServerResponse> delete(ServerRequest serverRequest) {
        String idStr = serverRequest.pathVariable("id");
        Long id = Long.valueOf(idStr);
        return TeacherRepository.findById(id)
                .flatMap(inDb ->
                        TeacherRepository.deleteById(id)
                        .then(ok().contentType(MediaType.APPLICATION_JSON).body(Mono.just(Result.success(inDb)),Result.class)))
                .switchIfEmpty(ok().contentType(MediaType.APPLICATION_JSON).body(Mono.just(Result.fail("未找到对应的数据")), Result.class));
    }

    public Mono<ServerResponse> queryById(ServerRequest serverRequest) {
        String id = serverRequest.pathVariable("id");
        return ok().contentType(MediaType.APPLICATION_JSON)
                .body(TeacherRepository.findById(Mono.just(Long.valueOf(id)))
                	.flatMap(tea -> Mono.just(Result.success(tea))), Result.class);
    }

    public Mono<ServerResponse> queryAll(ServerRequest serverRequest) {
        return ok().contentType(MediaType.APPLICATION_JSON)
                .body(TeacherRepository.findAll()
                	.collectList().flatMap(teaList -> Mono.just(Result.success(teaList))), Result.class);
    }

}

(三)参数校验自行代码实现

spring webflux的RouterFunction模式下暂时无法通过@Valid注解方式在入口处进行校验,故对参数校验需要自行实现,并嵌入到业务代码逻辑中:

import com.abinge.boot.staging.exceptions.BizException;
import com.abinge.boot.staging.model.Student;
import com.abinge.boot.staging.model.Teacher;
import org.apache.commons.lang3.StringUtils;

public class ParamCheckUtil {
    public static void checkSaveStudent(Student student){
        if (StringUtils.isBlank(student.getName())){
            throw new BizException("name can not be blank");
        }
        if (StringUtils.isBlank(student.getAge())){
            throw new BizException("age can not be blank");
        }
    }

    public static void checkSaveTeacher(Teacher teacher) {
        if (StringUtils.isBlank(teacher.getName())){
            throw new BizException("name can not be blank");
        }

    }
}

(四)全局异常处理通过WebExceptionHandler实现

原有通过org.springframework.web.bind.annotation.RestControllerAdvice注解来实现的全局异常切面处理,在spring webflux的RouterFunction模式下已无法使用了,故通过实现WebExceptionHandler的自定义异常处理handler来实现全局异常处理,同时通过@Order(-2)来提升我们自定义异常处理器的执行顺序,值越小优先级越高:

import com.abinge.boot.staging.model.Result;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;


@Slf4j
@Component
@Order(-2)
public class ExeptionHandler implements WebExceptionHandler {

    @Override
    public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) {
        log.error("process system error", throwable);
        ServerHttpResponse response = serverWebExchange.getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        DataBuffer wrap = response.bufferFactory().wrap(JSON.toJSONString(Result.fail(throwable.getMessage())).getBytes());
        return response.writeWith(Mono.just(wrap));
    }
}

(五)POM依赖取消已经不再使用的aop和validation的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.abinge.boot</groupId>
    <artifactId>staging</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>staging</name>
    <description>abinge demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-r2dbc</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.miku</groupId>
            <artifactId>r2dbc-mysql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

至此,基于通过RouterFunction模式将spring mvc项目改造为了spring webflux的项目已经完成。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值