Spring5新功能

新功能概述

  1. 整个 Spring5 框架的代码基于Java8 实现,运行时兼容 JDK9,许多不建议使用的类和方法在代码库中已经被删除了

  2. Spring 5 框架自带了通用的日志封装

  3. Spring 5 框架的核心容器支持 @Nullable 注解(@Nullable 注解可以使用在方法、属性、参数上)

    添加在方法上:表示该方法返回值可以为空

    添加在属性上:表示该属性值可以为空

    添加在参数上:表示该参数值可以为空

  4. Spring5 核心容器支持函数式风格 GenericApplicationContext / AnnotationConfigApplicationContext

  5. Spring5 框架支持整合 JUnit5 单元测试框架

  6. Spring5 框架提供了一个新的模块:WebFlux

Spring5 整合日志框架

  1. Spring5 已经移除了 Log4jConfigListener,官方建议使用 Log4j2
  2. 如果想要使用 Log4j,那么就需要将 Spring 的版本降到 4 或 4 之前的版本

Ps: 下面演示的代码还是使用的事务管理时编写的代码

实现步骤

回顾:之前的代码架构

在这里插入图片描述

第一步:引入相关 jar 包

在这里插入图片描述

在这里插入图片描述

第二步:创建 log4j2.xml 配置文件(该配置文件的名字的固定的)

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出(越往右边的级别打印的就越多)-->
<configuration status="INFO">
    <!--先定义所有的appender-->
    <appenders>
        <!--输出日志信息到控制台-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>

    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

注意:该文件的名称只能是 log4j2.xml,不能随意取名

第三步:测试效果

package com.laoyang.spring.test;

import com.laoyang.spring.config.TxConfig;
import com.laoyang.spring.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.transaction.PlatformTransactionManager;

public class UserTest {
    @Test
    public void testTransferAccounts3() {
        ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
        UserService userService = context.getBean("userService", UserService.class);

        userService.transferAccounts();
    }
}

在这里插入图片描述

Spring5 自带的日志功能

package com.laoyang.spring.test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogTest {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogTest.class);

    public static void main(String[] args) {
        LOGGER.info("hello");

        LOGGER.warn("world");
    }
}

导的包一定要是 slf4j 下的,不要导错了!!!

在这里插入图片描述

Spring 5 框架的核心容器支持 @Nullable 注解

  • @Nullable 注解可以使用在方法、属性、参数上
  • 添加在方法上:表示该方法返回值可以为空
  • 添加在属性上:表示该属性值可以为空
  • 添加在参数上:表示该参数值可以为空
package com.laoyang.spring.test;

import org.junit.Test;
import org.junit.rules.TestName;
import org.springframework.lang.Nullable;

/**
 * @ClassName NullableTest
 * @Description: @Nullable 注解的使用
 * @Author Laoyang
 * @Date 2021/12/22 11:25
 */
public class NullableTest {
    @Nullable
    private String name;

    @Nullable
    public String getName() {
        return name;
    }

    public void getNames(@Nullable String a) {
        System.out.println(a);
    }
}

Spring5 核心容器支持函数式风格 GenericApplicationContext/AnnotationConfigApplicationContext

package com.laoyang.spring.test;

import com.laoyang.spring.config.TxConfig;
import com.laoyang.spring.service.UserService;
import org.junit.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

/**
 * @ClassName FunctionTest
 * @Description: Spring5 核心容器支持函数式风格
 * @Author Laoyang
 * @Date 2021/12/22 11:51
 */
public class FunctionTest {
    public static void main(String[] args) {
        // 这种创建对象的方式是靠我们自己手动 new 出来的,Spring 是不能直接进行管理的
        FunctionTest functionTest = new FunctionTest();

        // 如果想要 Spring 进行管理,则需要进行注册
    }
}

GenericApplicationContext

package com.laoyang.spring.test;

import com.laoyang.spring.config.TxConfig;
import com.laoyang.spring.service.UserService;
import org.junit.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

/**
 * @ClassName FunctionTest
 * @Description: Spring5 核心容器支持函数式风格
 * @Author Laoyang
 * @Date 2021/12/22 11:51
 */
public class FunctionTest {
    /**
     * 使用函数式风格创建对象,交给 Spring 进行管理
     */
    @Test
    public void testGenericApplicationContext() {
        // 1、创建 GenericApplicationContext 对象
        GenericApplicationContext context = new GenericApplicationContext();

        // 2、调用 context 的方法进行对象注册
        context.refresh();
//        context.registerBean(FunctionTest.class, () -> new FunctionTest());
        context.registerBean("function", FunctionTest.class, () -> new FunctionTest());

        /*
         3、获取 Spring 注册的对象
         这里的对象和之前的写法有一点不同,之前默认的对象名是类名首字母小写,但是这里的不同
         这里有两种方式可以使用:
         第一种:写类的全路径
         第二种:在注册对象的时候手动设置对象名(在 context.registerBean() 方法中设置)
         */
        // 第一种写法:类的全路径
//        FunctionTest functionTest = (FunctionTest) context.getBean("com.laoyang.spring.test.FunctionTest");

        // 第二种写法:写我们注册对象时设置的名称
        FunctionTest functionTest = (FunctionTest) context.getBean("function");
        System.out.println(functionTest);
    }
}

AnnotationConfigApplicationContext

  • 这种方式和上面那种差不多
package com.laoyang.spring.test;

import com.laoyang.spring.config.TxConfig;
import com.laoyang.spring.service.UserService;
import org.junit.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

/**
 * @ClassName FunctionTest
 * @Description: Spring5 核心容器支持函数式风格
 * @Author Laoyang
 * @Date 2021/12/22 11:51
 */
public class FunctionTest {
    @Test
    public void testAnnotationConfigApplicationContext() {
        // 1、创建 AnnotationConfigApplicationContext 对象
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        
        // 2、调用 context 的方法进行对象注册
        context.refresh();
//        context.registerBean(FunctionTest.class, () -> new FunctionTest());
        context.registerBean("function", FunctionTest.class, () -> new FunctionTest());

        /*
         3、获取 Spring 注册的对象
         这里的对象和之前的写法有一点不同,之前默认的对象名是类名首字母小写,但是这里的不同
         这里有两种方式可以使用:
         第一种:写类的全路径
         第二种:在注册对象的时候手动设置对象名(在 context.registerBean() 方法中设置)
         */
        // 第一种写法:类的全路径
//        FunctionTest functionTest = (FunctionTest) context.getBean("com.laoyang.spring.test.FunctionTest");

        // 第二种写法:写我们注册对象时设置的名称
        FunctionTest functionTest = (FunctionTest) context.getBean("function");
        System.out.println(functionTest);
    }
}

整合 JUnit5 单元测试框架

整合 JUnit4

第一步:引入 Spring 相关针对测试的 jar 包

在这里插入图片描述

第二步:创建测试类,使用注解方式完成

package com.laoyang.spring.test;

import com.laoyang.spring.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @ClassName JUnit4Test
 * @Description: 整合 JUnit4
 * @Author Laoyang
 * @Date 2021/12/22 16:12
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean1.xml")
public class JUnit4Test {
    /*
    @RunWith():指定 JUnit 版本
    @ContextConfiguration():加载配置文件
     */

    // 注入 service
    @Autowired
    private UserService userService;

    @Test
    public void testJUnit4() {
        userService.transferAccounts();
    }
}

测试前还需要将配置类中的 @Configuration 注解注释掉,不然会导致有 bean 不唯一,然后报错

整合 JUnit5

第一步:引入 JUnit5 相关 jar 包

在这里插入图片描述

第二步:创建测试类,使用注解完成

package com.laoyang.spring.test;

import com.laoyang.spring.service.UserService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

/**
 * @ClassName JUnit5Test
 * @Description: 整合 JUnit5
 * @Author Laoyang
 * @Date 2021/12/22 16:23
 */
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean1.xml")
public class JUnit5Test {
/*
    @ExtendWith():使用该注解引用 SpringExtension
    > SpringExtension: 是JUnit多个可拓展API的一个实现,提供了对现存Spring TestContext Framework的支持
    @ContextConfiguration():加载配置文件
     */

    // 注入 service
    @Autowired
    private UserService userService;

    @Test
    public void testJUnit4() {
        userService.transferAccounts();
    }
}

可以使用一个复合注解替代类上的两个注解:@SpringJunitConfig

package com.laoyang.spring.test;

import com.laoyang.spring.service.UserService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

/**
 * @ClassName JUnit5Test
 * @Description: 整合 JUnit5
 * @Author Laoyang
 * @Date 2021/12/22 16:23
 */
//@ExtendWith(SpringExtension.class)
//@ContextConfiguration("classpath:bean1.xml")
@SpringJUnitConfig(locations = "classpath:bean1.xml")
public class JUnit5Test {
/*
    @ExtendWith():使用该注解引用 SpringExtension
    > SpringExtension: 是JUnit多个可拓展API的一个实现,提供了对现存Spring TestContext Framework的支持
    @ContextConfiguration():加载配置文件

    @SpringJUnitConfig(locations = "classpath:bean1.xml"):复合注解
     */

    // 注入 service
    @Autowired
    private UserService userService;

    @Test
    public void testJUnit4() {
        userService.transferAccounts();
    }
}

SpringWebflux

说明

  • 官方文档https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html

  • SpringWebflux 是 Spring5 中添加的模块,用于 Web 开发,功能和 SpringMVC 类似

  • 传统的 Web 框架(比如:SpringMVC)是基于 Servlet 容器实现的;而 Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才会支持使用(Servlet3.1之前的版本不能使用 ),核心就是基于 Reactor 相关 API 实现的

  • 异步非阻塞

    同步和异步针对的是调用者,比如调用者发送请求,如果等着对方回应之后才去做其他事情就是同步;如果发送请求之后不等对方回应就去做其它事情就是异步。

    非阻塞和阻塞针对的是被调用者,如果被调用者收到请求之后,并做完请求任务之后才给出反馈,那么就是阻塞;如果收到请求之后马上给出反馈然后再去做其它事情,那么就是非阻塞。

Webflux 特点

  • 异步非阻塞:好处:在有限的资源下提高系统的吞吐量和伸缩性,以 Reacror 为基础实现响应式编程
  • 函数式编程:Spring5 框架基于 Java8,Webflux 可以使用 Java8 函数式编程方式实现路由请求

Webflux 和 SpringMVC 的区别

在这里插入图片描述

  • 两个框架都可以使用注解方式进行操作,都可以运行在 Tomcat 等容器中
  • SpringMVC 采用的是命令式编程(一行一行代码执行),而 Webflux 采用的是异步响应式编程
  • 关于什么时候使用哪个框架,大家可查看官方文档 1.1.4,也可看下图了解一下(图片是官方截取的)

在这里插入图片描述

响应式编程

说明

  • 响应式编程:响应式编程是一种面向数据流和变化传播的编程范式,这意味着可以在编程语言中很方便的表达静态或动态的数据流,而相关的计算模型会自动变化的值通过数据流进行传播。

    以电子表格为例,单元格可以包含字面值或类似 ”B1+C1“ 的公式,而包含公式的单元格的值会依据其它单元格的值的变化而变化

Java8 及其之前版本的实现方式

  • Java8 及其之前版本中提供了观察者模式的两个类:ObserverObservable

  • Java9 及其之后版本使用 Flow 类取代了 Java8 及之前版本的那两个类

  • 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

    比如军营中的哨兵,哨兵在军营的各个方位,然后一旦发现情况则告知主帅,然后由主帅发布命令(进攻、防守等)

    主帅是一,哨兵是多

创建 SpringBoot 工程(因为 SpringBoot 使用起来比较方便)

在这里插入图片描述

选择 Spring Initializr 之后就下一步,然后设置对应的包路径,然后一直下一步就好了

编写对应代码进行测试,查看对应的效果

package com.laoyang.spring5_demo7.reactor8;

import java.util.Observable;

/**
 * @ClassName ObserverDemo
 * @Description: 观察者模式演示
 * @Author Laoyang
 * @Date 2021/12/22 20:58
 */
public class ObserverDemo extends Observable {

    public static void main(String[] args) {
        ObserverDemo observer = new ObserverDemo();
        // 添加观察者
        observer.addObserver((o, arg) -> {
            System.out.println("发生了变化");
        });

        observer.addObserver((o, arg) -> {
            System.out.println("收到被观察者的通知,准备改变");
        });

        // 将此 Observable 对象标记为已更改
        observer.setChanged();

        // 如果该 Observable 对象发生了变化,则通知所有观察者
        observer.notifyObservers();
    }
}

Reactor 实现响应式编程

  • 在响应式编程的操作中,都需要满足 Reactive 规范,Reactor 就是满足这种规范的

  • 在 Reactor 中有两个核心类:Mono 和 Flux,这两个类都实现了 Publisher 接口,都提供了丰富的操作符

    Mono 对象实现发布者,返回 0 或 1 个元素

    Flux 对象实现发布者,返回 n 个元素

  • Mono 和 Flux 都是数据流的发布者,使用 Mono 和 Flux 都可以发出三种数据信号:元素值、错误信号、完成信号

    错误信号和完成信号都表示终止信号,终止信号用于告诉订阅者数据流结束了

    错误信号终止数据流时会把错误信息传递给订阅者

代码演示(Flux 和 Mono)

第一步:引入Reactor 相关依赖

<?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 https://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.6.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.laoyang</groupId>
	<artifactId>spring5_demo7</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring5_demo7</name>
	<description>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</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- Reactor 相关依赖 -->
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-core</artifactId>
			<version>3.4.6</version>
		</dependency>
	</dependencies>

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

第二步:代码演示

package com.laoyang.spring5_demo7.reactor8;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * @ClassName ReactorDemo
 * @Description: Flux 和 Mono 的演示
 * @Author Laoyang
 * @Date 2021/12/23 15:58
 */
public class ReactorDemo {
    /**
     * 说明:
     * 调用 just() 方法或者其它方法只是声明了数据流,并没有发出,
     * 只有进行订阅之后才会触发数据流,不订阅就什么都不会发生。
     *
     * 订阅方法:subscribe()
     */
    public static void main(String[] args) {
        // just() 方法可以直接声明元素
//        Flux.just(1, 2, 3);
//        Mono.just(1);
        Flux.just(1, 2, 3).subscribe(System.out::print);
        Mono.just(1).subscribe(System.out::print);

        // 其它方法
        // 数组元素
        Integer[] arr = {11, 22, 33};
        Flux.fromArray(arr);

        // 集合元素
        List<Integer> list = Arrays.asList(arr);
        Flux.fromIterable(list);

        // Stream 流元素
        Stream stream = list.stream();
        Flux.fromStream(stream);
    }
}

因为 Mono 和 Flux 都实现了 Publisher 接口,所以它们使用的方法基本也是一样的,所以后面就没有演示 Mono 的调用了

三种信号的特点

  • 错误信号和完成信号都是终止信号,不能同时存在
  • 如果没有发送任何元素值,而是直接发送错误信号或完成信号,那么就表示是一个空数据流
  • 如果没有错误信号,也没有完成信号,那么就表示是一个无限数据流

操作符

  • 对数据流进行一道道操作,就称为操作符(比如工厂中的流水线)

  • 常用操作符

    map:将元素映射为新元素

    flatMap:将元素映射为流(把每个元素转换为流,在转换之后多个流合并为一个大的流)

Webflux 执行流程和核心 API

  • Webflux 基于 Reactor 实现,默认容器是 Netty,Netty 是高性能、异步非阻塞的框架

  • SpringWebflux 执行过程和 SpringMVC 相似

    SpringWebflux 核心控制器 DispatchHandler,实现 WebHandler接口

    WebHandler 接口中有一个方法:handle()

    在这里插入图片描述

  • SpringWebflux 中的 DispatcherHandler 负责请求的处理,SpringWebflux 中还有三个组件:

    HandlerMapping:请求查询的处理方法

    HandlerAdapter:适配器,真正负责请求处理

    HandlerResultHandler:响应结果的处理

  • SpringWebflux 实现函数式编程的两个接口RouterFunction(路由处理)HandlerFunction(处理函数)

SpringWebflux(基于注解编程模型实现)

说明

  • SpringWebflux 实现方式有两种:注解编程模型和函数式编程模式

  • 注解方式和 SpringMVC 类似,只需要把相关依赖配置到项目中

    因为使用的是 SpringBoot 工程,而 SpringBoot 会自动配置相关运行容器,默认情况下就是使用 Netty 服务器

具体实现

第一步:创建 SprtingBoot 工程,引入相关依赖

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<!-- SpringBoot2.0.* 及以上版本使用的就是 Sping5 -->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<groupId>com.laoyang</groupId>
	<artifactId>spring</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring</name>
	<description>Demo project for Spring Boot</description>

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

	<dependencies>
        <!-- 这里改成 spring-boot-starter-webflux -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-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>

第二步:在 application.properties 文件中配置启动的端口号

server.port=8081

第三步:创建包(entity、service、controller)和相关类

用户实体类

package com.laoyang.spring.entity;

public class User {
    private String name;
    private String sex;
    private Integer age;

    public User() {
    }

    public User(String name, String sex, Integer age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

用户操作接口

package com.laoyang.spring.service;

import com.laoyang.spring.entity.User;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface UserService {
    /**
     * 根据id查询用户
     */
    Mono<User> findUserById(Integer id);

    /**
     * 查询所有用户
     */
    Flux<User> findAllUser();

    /**
     * 添加用户
     */
    Mono<Void> saveUser(Mono<User> user);
}

接口实现类

package com.laoyang.spring.service.impl;

import com.laoyang.spring.entity.User;
import com.laoyang.spring.service.UserService;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;

@Service
public class UserServiceImpl implements UserService {
    // 创建 map 集合存储数据(因为这里不操作数据库,所以就使用 map 来代替数据库)
    private final Map<Integer, User> userMap = new HashMap<>();

    /**
     * 初始化map
     */
    public UserServiceImpl() {
        this.userMap.put(1, new User("Tom", "男", 18));
        this.userMap.put(2, new User("Jerry", "男", 72));
        this.userMap.put(3, new User("lucy", "女", 36));
    }

    /**
     * 根据id查询用户
     */
    @Override
    public Mono<User> findUserById(Integer id) {
        return Mono.justOrEmpty(this.userMap.get(id));
    }

    /**
     * 查询所有用户
     */
    @Override
    public Flux<User> findAllUser() {
        return Flux.fromIterable(this.userMap.values());
    }

    /**
     * 新增用户
     */
    @Override
    public Mono<Void> saveUser(Mono<User> user) {
        // doOnNext() 方法相当于把 user 进行遍历,拿到里面的值,然后在把里面的值存到 userMap 中
        return user.doOnNext(person -> {
            // 向 map 对象中存值,为了 key 值不重复,可以使用当前 map 长度作为 key
            int id = this.userMap.size() + 1;
            this.userMap.put(id, person);
        // 终止信号
        }).thenEmpty(Mono.empty());
    }
}

第四步:创建 Controller

package com.laoyang.spring.controller;

import com.laoyang.spring.entity.User;
import com.laoyang.spring.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
public class UserController {
    // 注入 service
    @Autowired
    private UserService userService;

    /**
     * 根据id查询用户
     */
    @GetMapping("/user/{id}")
    public Mono<User> getUserId(@PathVariable Integer id) {
        return userService.findUserById(id);
    }

    /**
     * 查询所有用户
     */
    @GetMapping("/user/all")
    public Flux<User> getUsers() {
        return userService.findAllUser();
    }

    /**
     * 新增用户
     */
    @PostMapping("/user/save")
    public Mono<Void> saveUser(@RequestBody User user) {
        Mono<User> mono = Mono.just(user);
        return userService.saveUser(mono);
    }
}

第五步:使用 PostMan 进行测试

在这里插入图片描述

在这里插入图片描述

emmmm 新增用户那个接口不太好查看测试之后的效果,因为是使用的 map 存储数据,大家可以替换成数据库数据,然后在数据库中查看数据,或者新增之后试着判断一下是否成功,然后返回一个提示。

说明:

  • SpringMVC 是使用同步阻塞的方式实现的,基于 SpringMVC + Servlet + Tomcat
  • SpringWebflux 是使用异步非阻塞的方式实现的,基于 SpringWebflux + Reactor + Netty

SpringWebflux(基于函数式编程模型实现)

说明

  • 在使用函数式编程模型操作的时候,需要自己初始化服务器
  • 基于函数式编程模型操作的时候,有两个核心接口RouterFunction(实现路由功能,请求转发给对应的 handler)HandlerFunction(处理请求生成响应的函数),核心任务就是定义两个函数式接口的实现并启动需要的服务器。
  • SpringWebflux 请求和响应不再是 ServletRequest 和 ServletResponse 了,而是 ServerRequestServerResponse

具体实现

第一步:把注解编程模型的工程复制一份,然后把 controller 删除(controller 包和里面的类都删掉)

在这里插入图片描述

第二步:创建 Handler(具体实现方法)

package com.laoyang.spring.handler;


import com.laoyang.spring.entity.User;
import com.laoyang.spring.service.UserService;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class UserHandler {
    private final UserService userService;

    public UserHandler(UserService userService) {
        this.userService = userService;
    }

    /**
     * 根据id查询用户
     */
    public Mono<ServerResponse> getUserById(ServerRequest request) {
        /*
         1、获取id
         pathVariable():获取接口路径中的值
         */
        Integer id = Integer.valueOf(request.pathVariable("id"));

        // 为了保证代码质量,最好加上空值处理
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();

        // 2、调用 service 中的方法得到对应数据
        Mono<User> user = this.userService.findUserById(id);

        /*
         3、将 user 进行转换
         使用 Reactor 操作符(flatMap)将 user 转换成流
         contentType:设置数据类型
         switchIfEmpty:判断数据是否为空,如果是空的,则返回 notFound 对象
         */
        return user.flatMap(
                person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)
        ).switchIfEmpty(notFound);
    }

    /**
     * 查询所有用户
     */
    public Mono<ServerResponse> getAllUser() {
        // 调用 servier 中的方法得到对应数据
        Flux<User> users = this.userService.findAllUser();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users, User.class);
    }

    /**
     * 新增用户
     */
    public Mono<ServerResponse> saveUser(ServerRequest request) {
        // 得到 User 对象
        Mono<User> mono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.saveUser(mono));
    }
}

第三步:初始化服务器,编写 Router

package com.laoyang.spring;

import com.laoyang.spring.handler.UserHandler;
import com.laoyang.spring.service.UserService;
import com.laoyang.spring.service.impl.UserServiceImpl;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;

public class Server {
    /**
     * 1、创建 Router 路由
     */
    public RouterFunction<ServerResponse> routerFunction() {
        // 创建 handler 对象
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler(userService);

        // 设置路由
        return RouterFunctions.route(
            /*
             GET("/user/{id}"):设置请求路径
             accept(APPLICATION_JSON):接收的数据格式
             handler::getUserById:调用的方法
             */
            GET("/user/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
                .andRoute(
            GET("/all").and(accept(APPLICATION_JSON)),handler::getAllUser)
                .andRoute(
            POST("/save").and(accept(APPLICATION_JSON)),handler::saveUser
        );
    }
}

后面测试发现 /user/{id} 和 /user/all 这些路径前面的一样,就会导致我测试 all 的时候也走 {id} 路径,然后就会报错,所以后面两个接口就不写 /user 了

第四步:创建服务器,完成适配

package com.laoyang.spring;

import com.laoyang.spring.handler.UserHandler;
import com.laoyang.spring.service.UserService;
import com.laoyang.spring.service.impl.UserServiceImpl;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.netty.http.server.HttpServer;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;

public class Server {
    /**
     * 1、创建 Router 路由
     */
    public RouterFunction<ServerResponse> routerFunction() {
        // 创建 handler 对象
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler(userService);

        // 设置路由
        return RouterFunctions.route(
            /*
             GET("/user/{id}"):设置请求路径
             accept(APPLICATION_JSON):接收的数据格式
             handler::getUserById:调用的方法
             */
            GET("/user/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
                .andRoute(
            GET("/all").and(accept(APPLICATION_JSON)),handler::getAllUser)
                .andRoute(
            POST("/save").and(accept(APPLICATION_JSON)),handler::saveUser
        );
    }

    /**
     * 2、创建服务器完成适配
     */
    public void createReatorServer() {
        // 路由和 handler 适配
        RouterFunction<ServerResponse> router = routerFunction();
        HttpHandler httpHandler = toHttpHandler(router);

        // 适配器,将路由和 handler 进行适配
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);

        // 创建服务器
        HttpServer httpServer = HttpServer.create();

        httpServer.handle(adapter).bindNow();
    }
}

第五步:调用方法启动项目,然后使用 PostMan 进行测试查看对应效果

package com.laoyang.spring;

import com.laoyang.spring.handler.UserHandler;
import com.laoyang.spring.service.UserService;
import com.laoyang.spring.service.impl.UserServiceImpl;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.netty.http.server.HttpServer;

import java.io.IOException;

import static org.springframework.http.MediaType.APPLICATION_JSON;
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.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;

public class Server {
    public static void main(String[] args) throws IOException {
        Server server = new Server();
        server.createReatorServer();
        System.out.println("enter to exit!");
        System.in.read();
    }

    /**
     * 1、创建 Router 路由
     */
    public RouterFunction<ServerResponse> routerFunction() {
        // 创建 handler 对象
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler(userService);

        // 设置路由
        return RouterFunctions.route(
            /*
             GET("/user/{id}"):设置请求路径
             accept(APPLICATION_JSON):接收的数据格式
             handler::getUserById:调用的方法
             */
            GET("/user/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
                .andRoute(
            GET("/all").and(accept(APPLICATION_JSON)),handler::getAllUser)
                .andRoute(
            POST("/save").and(accept(APPLICATION_JSON)),handler::saveUser
        );
    }

    /**
     * 2、创建服务器完成适配
     */
    public void createReatorServer() {
        // 路由和 handler 适配
        RouterFunction<ServerResponse> router = routerFunction();
        HttpHandler httpHandler = toHttpHandler(router);

        // 适配器,将路由和 handler 进行适配
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);

        // 创建服务器
        HttpServer httpServer = HttpServer.create();

        httpServer.handle(adapter).bindNow();
    }
}

注意:因为我们没有设置端口号,所以每次启动都会有一个随机的端口号,我们需要使用这个随机的端口号进行测试

在这里插入图片描述

效果如下

在这里插入图片描述

在这里插入图片描述

使用 WebClinet 进行调用

package com.laoyang.spring;

import com.laoyang.spring.entity.User;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;

import java.util.List;

/**
 * @ClassName Client
 * @Description:使用 WebClinet 进行调用
 * @Author Laoyang
 * @Date 2021/12/23 22:05
 */
public class Client {
    public static void main(String[] args) {
        // 调用服务器地址
        WebClient webClient = WebClient.create("http://localhost:2888");

        // 根据 id 查询
        String id = "1";
        /*
        get():接口请求
        uri("/user/", id):请求路径和参数
        accept(MediaType.APPLICATION_JSON):接收的数据格式
        retrieve():初始化
        bodyToMono:转换为指定对象
         */
        User user = webClient.get().uri("/user/{id}", id).
                accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();

        System.out.println(user.getName());

        // 查询所有用户
        Flux<User> result = webClient.get().uri("/all").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);

        result.map(stu -> stu.getName()).buffer().doOnNext(System.out::println).blockFirst();
    }
}

先启动 Server 类中的 main 方法,然后将随机生成的端口号设置好,在启动这个 main 方法

效果如下

在这里插入图片描述

结构展示

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值