• Spring WebFlux 实践


    WebFlux 学习之路

    本文借鉴于 Spring WebFlux 官方文档 ,对 Spring WebFlux进行学习、练习它的功能。涉及到 Reactive 的核心Api操作,操作数据库、Redis、Http请求等方面。

    本文的代码仓库:
    https://gitee.com/fengsoshuai/webflux-demo

    以下说明只粘贴了部分代码,全部代码,需要童鞋去仓库下载哦。

    1 、WebFlux 简介

    Spring WebFlux 是Spring 框架在 5.0版本之后推出的,异步响应式框架。可以不需要 Servlet Api,也就是能够不使用 Spring Mvc就能对外暴露接口。通过 Reactor 项目,实现了相关的流式操作。

    一般有两种写法。

    一种是使用 Reactor 提供的流式Api,同时使用 Spring Mvc 的相关注解,使用上和 Spring Mvc 差异性很小。主要在于它返回的数据类型变成了 WebFlux 中的 MonoFlux 了。具体见本项目中对应的模块《webflux-hello-web-demo》。

    另一种写法是使用配置的方式,使用 @Confuguration ,并在该配置类中配置 Bean,返回RouterFunction对象。该对象可以有多个,存在优先级时可以使用 @Order 注解。该对象的作用是配置了路由,请求参数的格式等。具体见本项目中对应的《webflux-hello-demo》模块。

    另外,Spring WebFlux 大量的使用了 流式API,如果对 Java 8 之后的 Stream 操作不熟悉的同学,可以先去了解 Stream 操作。可以参考一下 这篇文章

    2、WebFlux 的数据库操作

    本文只探讨对mysql的操作。不同于常规的jdbc的方式,当使用响应式编程对数据库操作时,也有对应的驱动,mysql 的驱动叫 r2dbc。

    它的主要特点是,从对数据库的连接,就开始返回了 Publisher 的数据类型。
    一般需要引入包:

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-r2dbc</artifactId>
            </dependency>
            <dependency>
                <groupId>dev.miku</groupId>
                <artifactId>r2dbc-mysql</artifactId>
                <version>0.8.2.RELEASE</version>
            </dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    WebFlux 实践内容

    项目结构如下:
    在这里插入图片描述

    1 、入门案例

    在Gitee 仓库中,对应的 webflux-hello-demo模块。完整代码请从代码仓库拉取。

    主要是使用 RouterFunction 和 处理器,来替代原先的 SpringMVC的映射界面路由的功能。

    1.1 RouterConfiguration

    package org.feng.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.feng.handler.RouterHandler;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.MediaType;
    import org.springframework.web.reactive.function.server.RequestPredicates;
    import org.springframework.web.reactive.function.server.RouterFunction;
    import org.springframework.web.reactive.function.server.RouterFunctions;
    import org.springframework.web.reactive.function.server.ServerResponse;
    
    /**
     * WebFlux 路由配置
     *
     * @version v1.0
     * @author: fengjinsong
     * @date: 2022年06月07日 21时03分
     */
    @Slf4j
    @Configuration
    public class RouterConfiguration {
        @Bean
        public RouterFunction<ServerResponse> routerMapper(RouterHandler handler) {
            log.info("注册路由...");
    
            // 这里在写完整后,可以考虑使用 import static 来简化代码;虽然牺牲了一部分可读性,但是一些无用代码确实能大面积减少
            return RouterFunctions
                    // 根路径
                    .nest(RequestPredicates.path("/webflux"),
                            RouterFunctions
                                    .route(RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), handler::hello)
                                    // 追加其他路由
                                    .andRoute(RequestPredicates.GET("/getEmpById").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), handler::getEmpById)
                    );
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    1.2 RouterHandler

    package org.feng.handler;
    
    import lombok.extern.slf4j.Slf4j;
    import org.feng.entity.Employee;
    import org.feng.entity.dto.EmployeeDTO;
    import org.feng.entity.dto.GetEmpRequestDTO;
    import org.feng.service.EmployeeService;
    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 javax.annotation.Resource;
    
    /**
     * 路由处理器
     *
     * @version v1.0
     * @author: fengjinsong
     * @date: 2022年06月07日 21时05分
     */
    @Slf4j
    @Component
    public class RouterHandler {
    
        @Resource
        private EmployeeService employeeService;
    
        /**
         * GET 请求 http://localhost/webflux/hello 时调用此方法;
         *
         * @param request 请求体
         * @return 响应
         */
        public Mono<ServerResponse> hello(ServerRequest request) {
            log.info("Hello Webflux 的请求体 {}", request);
            return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue("Hello Webflux");
        }
    
        /**
         * 通过 HTTP 请求的入参id,获得想要的员工的数据
         *
         * @param request 请求体
         * @return 响应
         */
        public Mono<ServerResponse> getEmpById(ServerRequest request) {
            log.info("getEmpById 的请求体 {}", request);
    
            // 将接口传入的参数,转换为 Mono 对象
            Mono<GetEmpRequestDTO> requestParam = request.bodyToMono(GetEmpRequestDTO.class);
            // 消费请求参数,从 业务实现中查找数据,并消费转换获得最终响应
            Mono<EmployeeDTO> response = requestParam.map(dto -> {
                Mono<Employee> empById = employeeService.getEmpById(dto.getId());
    
                EmployeeDTO responseEmp = new EmployeeDTO();
                if (!Mono.empty().equals(empById)) {
                    // 消费数据
                    empById.subscribe(resp -> {
                        responseEmp.setName(resp.getName());
                        responseEmp.setAge(resp.getAge());
                        responseEmp.setBirthday(resp.getBirthday());
                        responseEmp.setJobNo(resp.getJobNo());
                        responseEmp.setHireDay(resp.getHireDay());
                    });
                }
                return responseEmp;
            });
            return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(response, EmployeeDTO.class);
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    2、操作Mysql

    这一部分主要是实现了 mysql 动态数据源的切换。
    并且整合了nacos做配置中心,主要是配置了mysql的连接信息。

    另外,练习了 Webclient 的常见使用。
    对应的代码模块是 webflux-hello-mysql-demo

    2.1 动态数据源配置

    package org.feng.config;
    
    import dev.miku.r2dbc.mysql.MySqlConnectionConfiguration;
    import dev.miku.r2dbc.mysql.MySqlConnectionFactory;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.lang.NonNull;
    import org.springframework.r2dbc.connection.lookup.AbstractRoutingConnectionFactory;
    import reactor.core.publisher.Mono;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.Resource;
    import java.time.Duration;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * Mysql 动态数据源
     *
     * @version v1.0
     * @author: fengjinsong
     * @date: 2022年06月14日 22时32分
     */
    @Slf4j
    @Configuration
    public class MySqlDynamicDatasourceConfig extends AbstractRoutingConnectionFactory {
        /**
         * 默认租户ID
         */
        private static final String DEFAULT_MERCHANT_ID = "master";
        /**
         * 租户Key,切换数据源时,Context对象设置的key
         */
        public static final String MERCHANT_KEY = "merchantKey";
        /**
         * 数据源连接工厂映射
         */
        private static final Map<Object, Object> CONNECTION_FACTORY_CACHE_MAP = new HashMap<>(8);
    
        @Resource
        private MysqlDatasourceProperties mysqlDatasourceProperties;
    
        @Resource
        private MysqlDatasourcePropertiesV2 mysqlDatasourcePropertiesV2;
    
        @PostConstruct
        private void init() {
            final List<Property> propertyList = mysqlDatasourcePropertiesV2.getPROPERTY_LIST();
            for (Property property : propertyList) {
                MySqlConnectionFactory connectionFactory = MySqlConnectionFactory.from(MySqlConnectionConfiguration.builder()
                        .host(property.getHost())
                        .port(property.getPort())
                        .username(property.getUsername())
                        .password(property.getPassword())
                        .database(property.getDatabase())
                        .connectTimeout(Duration.ofSeconds(property.getConnectTimeout()))
                        .build());
    
                log.info("注册数据源 租户:{}  数据库名:{}", property.getMerchantKey(), property.getDatabase());
                CONNECTION_FACTORY_CACHE_MAP.put(property.getMerchantKey(), connectionFactory);
            }
            setTargetConnectionFactories(CONNECTION_FACTORY_CACHE_MAP);
            setDefaultTargetConnectionFactory(CONNECTION_FACTORY_CACHE_MAP.get(DEFAULT_MERCHANT_ID));
        }
    
        @NonNull
        @Override
        protected Mono<Object> determineCurrentLookupKey() {
            return Mono.deferContextual(Mono::just).handle((context, sink) -> {
                Object merchantKey = context.getOrDefault(MERCHANT_KEY, DEFAULT_MERCHANT_ID);
                log.info("使用数据源 {} HashCode {}", merchantKey, CONNECTION_FACTORY_CACHE_MAP.get(merchantKey).hashCode());
                assert merchantKey != null;
                sink.next(merchantKey);
            });
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    2.2 事务配置

    package org.feng.config;
    
    import io.r2dbc.spi.ConnectionFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.r2dbc.connection.R2dbcTransactionManager;
    import org.springframework.transaction.ReactiveTransactionManager;
    import org.springframework.transaction.reactive.TransactionalOperator;
    
    /**
     * 事务配置
     *
     * @version v1.0
     * @author: fengjinsong
     * @date: 2022年06月15日 20时44分
     */
    @Configuration
    public class R2dbcTransactionConfiguration {
        @Bean
        public ReactiveTransactionManager transactionManager(ConnectionFactory connectionFactory) {
            return (new R2dbcTransactionManager(connectionFactory));
        }
    
        @Bean
        public TransactionalOperator transactionalOperator(ReactiveTransactionManager transactionManager) {
            return TransactionalOperator.create(transactionManager);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2.3 DAO 操作

    package org.feng.repository;
    
    import org.feng.entity.Student;
    import org.springframework.data.r2dbc.repository.Query;
    import org.springframework.data.repository.reactive.ReactiveCrudRepository;
    import org.springframework.stereotype.Repository;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    /**
     * 学生表-数据库操作
     *
     * @version v1.0
     * @author: fengjinsong
     * @date: 2022年06月11日 11时01分
     */
    @Repository
    public interface StudentRepository extends ReactiveCrudRepository<Student, Integer> {
        /**
         * 按照学生ID查找
         *
         * @param id 学生ID
         * @return 对应ID的学生信息
         */
        Mono<Student> getStudentById(Integer id);
    
        /**
         * 通过学生姓名,模糊查询
         *
         * @param name 学生姓名的一部分
         * @return 学生列表
         */
        @Query("select id, name, age, weight, height from student where name like :name")
        Flux<Student> getStudentsByName(String name);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2.4 Service操作

    这一步使用了两种操作数据库的方式。

    
    package org.feng.service;
    
    import org.feng.config.MySqlDynamicDatasourceConfig;
    import org.feng.entity.Student;
    import org.feng.repository.StudentRepository;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
    import org.springframework.data.relational.core.query.Criteria;
    import org.springframework.data.relational.core.query.Query;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    /**
     * 学生业务实现
     *
     * @version v1.0
     * @author: fengjinsong
     * @date: 2022年06月11日 11时11分
     */
    @Service
    public class StudentServiceImpl implements StudentService {
        @Resource
        private StudentRepository studentRepository;
    
        @Resource
        private R2dbcEntityTemplate r2dbcEntityTemplate;
    
        @Override
        public Mono<Student> getById(Integer id) {
            return studentRepository.getStudentById(id);
        }
    
        @Override
        public Flux<Student> getStudentsByName(String merchantKey, String name) {
            return studentRepository.getStudentsByName("%" + name + "%")
                    .contextWrite(context -> context.put(MySqlDynamicDatasourceConfig.MERCHANT_KEY, merchantKey));
        }
    
        @Transactional(rollbackFor = RuntimeException.class)
        @Override
        public Mono<Student> saveStudent(Student student) {
            return studentRepository.save(student);
        }
    
        @Transactional(rollbackFor = RuntimeException.class)
        @Override
        public Flux<Student> saveStudent(List<Student> studentList) {
            return studentRepository.saveAll(studentList);
        }
    
        @Override
        @Transactional(rollbackFor = RuntimeException.class)
        public Mono<Void> deleteStudent(Integer id) {
            return studentRepository.deleteById(id);
        }
    
        @Override
        public Flux<Student> selectStudent(Integer currentPage, String subName) {
            // 设置每页显示2条数据
            return r2dbcEntityTemplate.select(
                    Query.query(Criteria.where("name").like("%" + subName + "%")).with(PageRequest.of(currentPage - 1, 2)),
                    Student.class);
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    3、WebClient的操作

    3.1 配置

    package org.feng.config;
    
    import io.netty.channel.ChannelOption;
    import io.netty.handler.timeout.ReadTimeoutHandler;
    import io.netty.handler.timeout.WriteTimeoutHandler;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.client.reactive.ClientHttpConnector;
    import org.springframework.http.client.reactive.ReactorClientHttpConnector;
    import org.springframework.http.client.reactive.ReactorResourceFactory;
    import org.springframework.web.reactive.function.client.WebClient;
    import reactor.netty.http.client.HttpClient;
    import reactor.netty.resources.ConnectionProvider;
    import reactor.netty.resources.LoopResources;
    
    import java.time.Duration;
    import java.time.temporal.ChronoUnit;
    import java.util.function.Function;
    
    /**
     * WebClient 配置,参考官方文档,https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client
     *
     * <br>参考:
     * <br> https://www.freesion.com/article/7381889590/
     * <br> https://blog.csdn.net/weixin_44266223/article/details/122967933
     *
     * @version V1.0
     * @author: fengjinsong
     * @date: 2022年06月16日 15时41分
     */
    @Configuration
    public class WebClientConfig {
    
        /**
         * 默认情况下,HttpClient参与持有的全局 Reactor Netty 资源 reactor.netty.http.HttpResources,包括事件循环线程和连接池<br>
         * 但是,这里选择不使用全局资源
         *
         * @return 资源工厂
         */
        private ReactorResourceFactory reactorResourceFactory() {
            ReactorResourceFactory factory = new ReactorResourceFactory();
            factory.setUseGlobalResources(false);
            // 设置一个loop进行http线程管理
            factory.setLoopResources(LoopResources.create("tcp-connect-loop", 30, true));
            // 配置固定大小连接池
            factory.setConnectionProvider(connectionProvider());
            return factory;
        }
    
        private ConnectionProvider connectionProvider() {
            return ConnectionProvider
                    .builder("tcp-connect-pool")
                    // 等待超时时间
                    .pendingAcquireTimeout(Duration.ofSeconds(6))
                    // 最大连接数
                    .maxConnections(30)
                    // 等待队列大小
                    .pendingAcquireMaxCount(300)
                    .maxIdleTime(Duration.ofSeconds(200))
                    .maxLifeTime(Duration.ofSeconds(200))
                    .build();
        }
    
        @Bean
        public WebClient webClient() {
            Function<HttpClient, HttpClient> mapper = client -> {
                // 连接超时时间
                client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
                        // 连接后的读、写超时
                        .doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(10))
                                .addHandlerLast(new WriteTimeoutHandler(10)))
                        // 设置响应超时时间
                        .responseTimeout(Duration.of(6, ChronoUnit.SECONDS))
                ;
                return client;
            };
    
            ClientHttpConnector connector = new ReactorClientHttpConnector(reactorResourceFactory(), mapper);
    
            return WebClient.builder()
                    // 编解码器对在内存中缓冲数据大小修改
                    .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(4 * 1024 * 1024))
                    .clientConnector(connector)
                    .build();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    3.2 具体操作

    package org.feng.controller;
    
    import org.feng.entity.Student;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.util.LinkedMultiValueMap;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.reactive.function.BodyInserters;
    import org.springframework.web.reactive.function.client.WebClient;
    import reactor.core.publisher.Mono;
    
    import javax.annotation.Resource;
    import java.time.LocalDate;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.ThreadLocalRandom;
    
    /**
     * 学习使用 webClient
     *
     * @version V1.0
     * @author: fengjinsong
     * @date: 2022年06月17日 09时03分
     */
    @RequestMapping("/webclient")
    @RestController
    public class StudentWebClientController {
        @Resource
        private WebClient webClient;
        private final String BASE_URL = "http://localhost/student";
    
        @GetMapping("/{id}")
        public Mono<Student> getOneByWebClient(@PathVariable("id") Integer id) {
            return webClient.get().uri(BASE_URL + "/{id}", id).accept(MediaType.APPLICATION_JSON)
                    .retrieve().bodyToMono(Student.class);
        }
    
        @GetMapping("/doPostSingle")
        public Mono<Student> doPostSingle() {
            Student student = new Student();
            student.setName("doPostSingle");
    
            return webClient.post().uri(BASE_URL + "/postSingle")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(student)
                    .retrieve()
                    .bodyToMono(Student.class);
        }
    
        @GetMapping("/doPostForm")
        public Mono<Student> doPostForm() {
            return webClient.post().uri(BASE_URL + "/postForm?studentName={studentName}&namePrefix={namePrefix}", "小明", LocalDate.now().toString())
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .retrieve()
                    .bodyToMono(Student.class);
        }
    
        /**
         * 请求体+路径参数
         */
        @GetMapping("/doPostRequestBody")
        public Mono<Student> doRequestBody() {
            Student student = new Student();
            student.setName("doPostRequestBody");
    
            int age = ThreadLocalRandom.current().nextInt(20, 30);
            return webClient.post().uri(BASE_URL + "/postRequestBody/{age}", age)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(BodyInserters.fromValue(student))
                    .retrieve()
                    .bodyToMono(Student.class);
        }
    
        /**
         * 请求体+URL参数
         */
        @GetMapping("/doRequestBodyWithParam")
        public Mono<Student> doRequestBodyWithParam() {
            Student student = new Student();
            student.setName("doRequestBodyWithParam");
            int id = ThreadLocalRandom.current().nextInt(20, 30);
            return webClient.post().uri(BASE_URL + "/postRequestBodyWithParam?id={id}", id)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(BodyInserters.fromValue(student))
                    .retrieve()
                    .bodyToMono(Student.class);
        }
    
        @GetMapping("/doFormRequest1")
        public Mono<Student> doFormRequest() {
            Map<String, Object> paramMap = new HashMap<>(4);
            paramMap.put("id", ThreadLocalRandom.current().nextInt(20, 30));
            paramMap.put("name", "小明");
    
            return webClient.post().uri(BASE_URL + "/formRequest1?name={name}&id={id}", paramMap)
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .retrieve()
                    .bodyToMono(Student.class);
        }
    
        @GetMapping("/doFormRequest2")
        public Mono<Student> testFormParam() {
            MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
            formData.add("name", "value1");
            formData.add("id", "23");
    
            return webClient.post()
                    .uri(BASE_URL + "/formRequest2")
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .body(BodyInserters.fromFormData(formData))
                    .retrieve()
                    .onStatus(HttpStatus::is4xxClientError, res -> Mono.error(new RuntimeException(res.statusCode() + "-自定义的")))
                    .onStatus(HttpStatus::is5xxServerError, res -> Mono.error(new RuntimeException(res.statusCode() + "自定义的")))
                    .bodyToMono(Student.class);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121

    注意事项

    在练习数据库操作时,需要连接数据库。
    我这里使用了Nacos做配置。
    项目中的 bootstrap.yml文件如下:

    spring:
      profiles:
        active: ${profiles.active:dev}
      application:
        # 应用名称
        name: webflux-demo
      cloud:
        nacos:
          config:
            # 启用 nacos 做配置
            enabled: true
            # 配置文件后缀
            file-extension: yml
            #  配置所在组
            group: fjs
            extension-configs:
              # nacos 中组为 fjs,data-id 是 datasource.yml
              - data-id: datasource.yml
                group: fjs
                # 配置热更新
                refresh: true
    debug: false
    # 端口
    server:
      port: 80
    ---
    spring:
      cloud:
        nacos:
          config:
            # nacos 用户名
            username: nacos
            # nacos 密码
            password: nacos
            # nacos 所在地址
            server-addr: localhost:80
            # 命名空间ID
            namespace: fjs
      config:
        activate:
          on-profile: dev
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    在这里插入图片描述
    在这里插入图片描述
    Nacos中的配置如下:

    dynamic:
      mysql:
        datasource:
          master:
            merchantKey: master
            host: localhost
            port: 3306
            username: root
            password: 123456
            database: dynamic_master1
            connectTimeout: 30
          slave:
            merchantKey: slave
            host: localhost
            port: 3306
            username: root
            password: 123456
            database: dynamic_slave1
            connectTimeout: 30
    
            
        map:
          master:
            - merchantKey=master
            - host=localhost
            - port=3306
            - username=root
            - password=123456
            - database=dynamic_master1
            - connectTimeout=30
          slave:
            - merchantKey=slave
            - host=localhost
            - port=3306
            - username=root
            - password=123456
            - database=dynamic_slave1
            - connectTimeout=30
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    学生表结构:

    /*
     Navicat Premium Data Transfer
    
     Source Server         : 阿里云
     Source Server Type    : MySQL
     Source Server Version : 80020
     Source Schema         : test
    
     Target Server Type    : MySQL
     Target Server Version : 80020
     File Encoding         : 65001
    
     Date: 11/06/2022 11:21:25
    */
    
    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for student
    -- ----------------------------
    DROP TABLE IF EXISTS `student`;
    CREATE TABLE `student`  (
      `id` int(0) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `age` int(0) NULL DEFAULT NULL,
      `weight` double NULL DEFAULT NULL,
      `height` double NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of student
    -- ----------------------------
    INSERT INTO `student` VALUES (1, '小明', 23, 33.2, 172.23);
    INSERT INTO `student` VALUES (2, '小明 79.1669237551011', 68, 79.1669237551011, 206.7008080419343);
    INSERT INTO `student` VALUES (3, '小明 74.1299037849883', 63, 74.1299037849883, 185.21078230412152);
    
    SET FOREIGN_KEY_CHECKS = 1;
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
  • 相关阅读:
    理解ROS tf
    【Java】中的String、StringBuffer和StringBuilder的区别
    优化理论12---- 既约梯度法
    jemalloc 5.3.0源码总结
    pycharm连接服务器
    can通信注意事项
    【广度优先搜索】leetcode 542. 01 矩阵
    搭建自己的OCR服务,第一步:选择合适的开源OCR项目
    77-基于51单片机的可调数控恒流源仿真(仿真+源码+论文)
    java中,通过替换word模板中的关键字后输出一个新文档
  • 原文地址:https://blog.csdn.net/FBB360JAVA/article/details/125547724