Spring5新特性

Spring5新特性

总览

https://cntofu.com/book/95/33-what-new-in-the-spring-framework.md

1、整个Spring5框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类个方法在代码库中删除

2、自带了通用的日志封装

​ 2.1、Spring5已经移除了Log4jConfigListener,官方建议使用Log4j2

​ 2.2、Spring框架整和Log4j2(下面讲怎么整合)

3、Spring5核心容器支持@Nullable注解

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

5、Spring5支持整合JUnit5框架

6、SpringWebFlux

6.1、[Spring WebFlux介绍](#一、Spring WebFlux介绍)

6.2、响应式编程

6.3、WebFlux执行流程和核心API

6.4、WebFlux基于注解编程模型的实现

6.5、WebFlux基于函数式编程模型的实现

Spring整合Log4j2

1、引入相关jar包

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.19</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.11.2</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>
    </dependencies>

2、创建log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
    日志级别以及优先级排序:OFF>FATAL>ERROR>WARN>INFO>DEBUG>TARCE>ALL>
-->
<configuration status="DEBUG">
    <!--定义所有的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才会生效
        root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出
    -->

    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

3、切面类

@Component
@Aspect
public class MyAspect {
    /**
     * 公共的切入点表达式
     */
    @Pointcut("execution(* org.spring.newfeatures.UserServiceImpl.*(..))")
    public void myPontcut() {

    }

    @Before(value = "myPontcut()")
    public void myBefore(JoinPoint joinPoint) {
        System.out.println( "前置通知 : " + joinPoint.getSignature().getName() );
    }

    @AfterReturning(value = "myPontcut()", returning = "ret")
    public void myAfterReturning(JoinPoint joinPoint, Object ret) {
        System.out.println( "后置通知 : " + joinPoint.getSignature().getName() + " , 返回值:" + ret );
        System.out.println( "==========" );
    }

    @Around(value = "myPontcut()")
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println( "环绕前" );
        //手动执行目标方法
        Object obj = joinPoint.proceed();
        System.out.println( "环绕后" );
        return obj;
    }

    @AfterThrowing(value = "myPontcut()", throwing = "e")
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println( "抛出异常通知 : " + e.getMessage() );
    }

    @After(value = "myPontcut()")
    public void myAfter(JoinPoint joinPoint) {
        System.out.println( "最终通知" );
    }
}

4、目标类

public interface UserService {
    public void addUser();
    public void updateUser();
    public void deleteUser();
}
@Service("userServiceImpl")
public class UserServiceImpl implements UserService {

    @Override
    public void addUser() {
        System.out.println("spring.anno.add");
    }

    @Override
    public void updateUser() {
        System.out.println("spring.anno.update");

    }

    @Override
    public void deleteUser() {
        System.out.println("spring.anno.delete");
        int i = 1/0;
    }
}

5、beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--扫描注解类-->
    <context:component-scan base-package="org.spring.newfeatures"/>
    <!--确定aop的注解要生效-->
    <aop:aspectj-autoproxy/>
</beans>

6、测试类

public class TestAspectjAnno {
    @Test
    public void demo01(){
        ClassPathXmlApplicationContext cpac
                    = new ClassPathXmlApplicationContext( "classpath:beans.xml" );
        UserService userService = (UserService)cpac.getBean( "userServiceImpl" );
        userService.addUser();
        userService.updateUser();
        userService.deleteUser();
    }
}

7、结果

2020-11-07 02:47:40,631 main DEBUG Apache Log4j Core 2.11.2 initializing configuration XmlConfiguration[location=F:\ideaproject\Spring5\target\classes\log4j2.xml]
2020-11-07 02:47:40,636 main DEBUG Installed 1 script engine
2020-11-07 02:47:40,915 main DEBUG Oracle Nashorn version: 1.8.0_251, language: ECMAScript, threading: Not Thread Safe, compile: true, names: [nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript], factory class: jdk.nashorn.api.scripting.NashornScriptEngineFactory
2020-11-07 02:47:40,915 main DEBUG PluginManager 'Core' found 117 plugins
2020-11-07 02:47:40,915 main DEBUG PluginManager 'Level' found 0 plugins
2020-11-07 02:47:40,918 main DEBUG PluginManager 'Lookup' found 13 plugins
2020-11-07 02:47:40,920 main DEBUG Building Plugin[name=layout, class=org.apache.logging.log4j.core.layout.PatternLayout].
2020-11-07 02:47:40,929 main DEBUG PluginManager 'TypeConverter' found 26 plugins
2020-11-07 02:47:40,942 main DEBUG PatternLayout$Builder(pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} -%msg%n", PatternSelector=null, Configuration(F:\ideaproject\Spring5\target\classes\log4j2.xml), Replace=null, charset="null", alwaysWriteExceptions="null", disableAnsi="null", noConsoleNoAnsi="null", header="null", footer="null")
2020-11-07 02:47:40,942 main DEBUG PluginManager 'Converter' found 44 plugins
2020-11-07 02:47:40,953 main DEBUG Building Plugin[name=appender, class=org.apache.logging.log4j.core.appender.ConsoleAppender].
2020-11-07 02:47:40,959 main DEBUG ConsoleAppender$Builder(target="SYSTEM_OUT", follow="null", direct="null", bufferedIo="null", bufferSize="null", immediateFlush="null", ignoreExceptions="null", PatternLayout(%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} -%msg%n), name="Console", Configuration(F:\ideaproject\Spring5\target\classes\log4j2.xml), Filter=null, ={})
2020-11-07 02:47:40,960 main DEBUG Starting OutputStreamManager SYSTEM_OUT.false.false
2020-11-07 02:47:40,961 main DEBUG Building Plugin[name=appenders, class=org.apache.logging.log4j.core.config.AppendersPlugin].
2020-11-07 02:47:40,962 main DEBUG createAppenders(={Console})
2020-11-07 02:47:40,962 main DEBUG Building Plugin[name=appender-ref, class=org.apache.logging.log4j.core.config.AppenderRef].
2020-11-07 02:47:40,965 main DEBUG createAppenderRef(ref="Console", level="null", Filter=null)
2020-11-07 02:47:40,966 main DEBUG Building Plugin[name=root, class=org.apache.logging.log4j.core.config.LoggerConfig$RootLogger].
2020-11-07 02:47:40,967 main DEBUG createLogger(additivity="null", level="INFO", includeLocation="null", ={Console}, ={}, Configuration(F:\ideaproject\Spring5\target\classes\log4j2.xml), Filter=null)
2020-11-07 02:47:40,970 main DEBUG Building Plugin[name=loggers, class=org.apache.logging.log4j.core.config.LoggersPlugin].
2020-11-07 02:47:40,971 main DEBUG createLoggers(={root})
2020-11-07 02:47:40,972 main DEBUG Configuration XmlConfiguration[location=F:\ideaproject\Spring5\target\classes\log4j2.xml] initialized
2020-11-07 02:47:40,972 main DEBUG Starting configuration XmlConfiguration[location=F:\ideaproject\Spring5\target\classes\log4j2.xml]
2020-11-07 02:47:40,972 main DEBUG Started configuration XmlConfiguration[location=F:\ideaproject\Spring5\target\classes\log4j2.xml] OK.
2020-11-07 02:47:40,974 main DEBUG Shutting down OutputStreamManager SYSTEM_OUT.false.false-1
2020-11-07 02:47:40,974 main DEBUG Shut down OutputStreamManager SYSTEM_OUT.false.false-1, all resources released: true
2020-11-07 02:47:40,974 main DEBUG Appender DefaultConsole-1 stopped with status true
2020-11-07 02:47:40,975 main DEBUG Stopped org.apache.logging.log4j.core.config.DefaultConfiguration@3dd3bcd OK
2020-11-07 02:47:41,013 main DEBUG Registering MBean org.apache.logging.log4j2:type=18b4aac2
2020-11-07 02:47:41,016 main DEBUG Registering MBean org.apache.logging.log4j2:type=18b4aac2,component=StatusLogger
2020-11-07 02:47:41,018 main DEBUG Registering MBean org.apache.logging.log4j2:type=18b4aac2,component=ContextSelector
2020-11-07 02:47:41,019 main DEBUG Registering MBean org.apache.logging.log4j2:type=18b4aac2,component=Loggers,name=
2020-11-07 02:47:41,020 main DEBUG Registering MBean org.apache.logging.log4j2:type=18b4aac2,component=Appenders,name=Console
2020-11-07 02:47:41,022 main DEBUG org.apache.logging.log4j.core.util.SystemClock does not support precise timestamps.
2020-11-07 02:47:41,023 main DEBUG Reconfiguration complete for context[name=18b4aac2] at URI F:\ideaproject\Spring5\target\classes\log4j2.xml (org.apache.logging.log4j.core.LoggerContext@c333c60) with optional ClassLoader: null
2020-11-07 02:47:41,023 main DEBUG Shutdown hook enabled. Registering a new one.
2020-11-07 02:47:41,024 main DEBUG LoggerContext[name=18b4aac2, org.apache.logging.log4j.core.LoggerContext@c333c60] started OK.
环绕前
前置通知 : addUser
spring.anno.add
后置通知 : addUser , 返回值:null
==========
最终通知
环绕后
环绕前
前置通知 : updateUser
spring.anno.update
后置通知 : updateUser , 返回值:null
==========
最终通知
环绕后
环绕前
前置通知 : deleteUser
spring.anno.delete
抛出异常通知 : / by zero
最终通知

将级别改为info

"C:\Program Files\Java\jdk1.8.0_251\bin\java.exe" ...
环绕前
前置通知 : addUser
spring.anno.add
后置通知 : addUser , 返回值:null
==========
最终通知
环绕后
环绕前
前置通知 : updateUser
spring.anno.update
后置通知 : updateUser , 返回值:null
==========
最终通知
环绕后
环绕前
前置通知 : deleteUser
spring.anno.delete
抛出异常通知 : / by zero
最终通知

java.lang.ArithmeticException: / by zero

Spring5核心容器支持@Nullable注解

作用位置

方法:表示返回值可以为null

org.springframework.context.ApplicationContext

image-20201107030407026

属性:表示属性值可以为null

image-20201107030648644

参数:表示参数值可以为null

org.springframework.context.annotation.AnnotationConfigApplicationContext

image-20201107030459943

Spring5函数式风格

函数式风格创建对象,交给spring容器进行管理。

演示

public class Spring5Test {
    @Test
    public void testGenericApplicationContext() {
        //1、创建GenericApplicationContext对象
        GenericApplicationContext gac = new GenericApplicationContext();
        //调用gac的方法进行对象的注册
        gac.refresh();
        //gac.registerBean( User.class, () -> new User() );
        ////获取user在spring注册的对象
        //User user = (User) gac.getBean( "org.spring5.ceshi.User" );
        
        //指定bean注册时的名字
        gac.registerBean( "user",User.class, () -> new User() );
        //获取user在spring注册的对象
        User user = (User) gac.getBean( "user" );
        System.out.println(user);
    }
}
public class User {

}

测试结果

org.spring5.ceshi.User@397fbdb

Spring5整合JUnit5

整合JUnit4

1、引入Spring针对测试的依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

2、创建测试类

package org.spring5.ceshi;

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

/**
 * @author Administrator
 * @author 2020-11-07
 * @Description
 **/
//指定用的JUnit版本
@RunWith(SpringJUnit4ClassRunner.class)
//指定配置文件路径
@ContextConfiguration("classpath:beans.xml")
public class JTest4 {

    @Autowired
    @Qualifier("userServiceImpl")
    private UserService userService;

    @Test
    public void testJuni4(){
        userService.addUser();
        userService.updateUser();
        userService.deleteUser();
    }

}

整合JUnit5

1、引入相关jar包

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.5.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

2、测试

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

/**
 * @author Administrator
 * @author 2020-11-07
 * @Description
 **/

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:beans.xml")
public class JTest5 {
    @Autowired
    @Qualifier("userServiceImpl")
    private UserService userService;

    @Test
    public void testJuni5(){
        userService.addUser();
        userService.updateUser();
        userService.deleteUser();
    }
}

3、总结

1、JUnit4和JUnit5在类上引入的注解不同

# JUnit4
@RunWith(SpringJUnit4ClassRunner.class)
# JUnit5
@ExtendWith(SpringExtension.class)

2、@Test注解导入的包不同

# JUnit4
import org.junit.Test;
# JUnit5
import org.junit.jupiter.api.Test;

3、JUnit5中还多了一个@@SpringJunitConfig注解,这是一个复合注解,用来替换@ExtendWith@ContextConfiguration,使用:

@SpringJUnitConfig(locations="classpath:beans.xml")

SpringWebFlux

一、Spring WebFlux介绍

1、SpringWebFlux是在Spring5中出现的一个新的模块,用于web开发。功能和SpringMVC类似,WebFlux基于一种当前比较流行的响应式编程方式而出现的框架

2、传统的web框架,如SpringMVC、Struts2,这些都是基于Servlet容器的。Webflux是一种异步非阻塞到的框架,Servlet3.1才开始支持这种架构,而webflux并不是基于servlet这种容器运行的,他的核心是基于Reactor(是Reactive中的一种具体的框架,Reactive是一种响应式编程框架)的相关API实现的。它支持在传统的Servlet容器中运行(如tomcat),还支持在Netty等容器中运行。

3、异步非阻塞

​ 异步和同步:异步和同步针对调用者,调用者发送请求,如果要等待对方回应后才能去做事情就是同步;不等待对方回应就去做事情,这就是异步

​ 非阻塞和阻塞:针对被调用者。被调用者收到请求之后,做完请求任务之后才给反馈就是阻塞,收到请求之后马上给出反馈然后再去做事情就是非阻塞。

webflux特点

1、非阻塞:在不提升硬件的情况下,能提高系统的吞吐量伸缩性,以Reactor为基础实现响应式编程。

2、函数式编程:Spring5基于java8,WebFlux使用Java8的一些函数式编程方式来实现路由请求

与SpringMVC的对比

spring mvc and webflux venn

1、两个框架都可以使用注解方式来编程,都可以运行在Tomcat、Jetty、Undertow等容器中

2、SpringMVC采用命令式编程(就是一行一行执行,便于程序的理解和调试),WebFlux异步采用响应式的编程方式。

如何选取这两种框架

1、不同的web项目两种都可以

2、如果项目中有远程服务调用的话,可以使用webflux框架,它能提高系统的吞吐量和系统的伸缩性。例如可以用在微服务网关上

二、响应式编程

什么是响应式编程?

响应式编程(Reactive Programming),简称RP,它是一种面向数据流和变化传播的编程方式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,二相关的计算模型会自动将变化的值通过数据流进行传播

代码演示

Java8提供了观察者模式的两个类Observer和Observable。

观察者模式:当一个对象被修改时,则会自动通知依赖它的对象。

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( "收到被观察者通知,准备改变" );
        } );
        //监控数据变化情况
        observer.setChanged();
        //通知改变
        observer.notifyObservers();
    }
}

Reactor实现响应式编程

1、响应式编程操作中,要满足Reactive这个规范,Reactor就是满足这个规范的框架。

2、Reactive有两个核心类,Mono和Flux,这个两个类都实现Publisher接口,这两个两个类中提供了丰富的操作符来实现函数式编程。Flux对象实现发布者,返回N个元素;Mono实现发布者,返回0或者1个元素。

3、Flux和Mono都是数据流的发布者,使用Flux和Mono都可以发出三种信号:错误信号、完成信号、元素值。错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者。

代码演示Flux和Mono

引入依赖

<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
    <version>3.1.5.RELEASE</version>
</dependency>
public class TestReactor {
    public static void main(String[] args) {
        //just方法直接声明元素,subscribe表示订阅输出
        Flux.just( 1, 2, 3, 4 ).subscribe(System.out::print);
        System.out.println("\n=========");
        Mono.just( 1 ).subscribe(System.out::println);
        System.out.println("=========");
        //其他方法
        Integer[] array = {1, 2, 3, 4};
        Flux.fromArray( array ).subscribe(System.out::print);
        System.out.println("\n=========");

        List<Integer> list = Arrays.asList( array );
        Flux.fromIterable( list ).subscribe(System.out::print);
        System.out.println("\n=========");

        Stream<Integer> stream = list.stream();
        Flux.fromStream( stream ).subscribe(System.out::print);
        System.out.println("\n=========");
        //错误信号
        Flux.error( new Exception("出现错误") ).subscribe(System.out::println);
    }
}

三种信号特点

1、错误信号和完成信号都是终止信号,不能共存

2、如果没有发送任何元素值,而是直接发送错误或者完成信号,表示空数据流

3、如果没有错误信号,没有完成信号,表示是无限数据流

调用just或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅后才会触发数据流,不订阅什么都不会发生

操作符

对数据流进行一次次操作,称为操作符。

1、map 元素映射成新的元素

image-20201107171942055

2、flatMap 元素映射成流

​ 把元素变成流,然后把这些流合并成一个大的流进行输出。

三、SpringWebFlux执行流程和核心API

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

SpringWebFlux执行过程

执行流程和SpringMVc类似。SpringWebFlux核心控制器DispatcherHandler,实现了一WebHandler接口

为了看源码,我们先改一下POM文件

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

我们先来看一下WebHadnler接口

public interface WebHandler {
   Mono<Void> handle(ServerWebExchange exchange);
}

这个接口有一个方法handle,我们查看实现类DispatcherHandler中,它的具体实现

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
   if (this.handlerMappings == null) {
      return createNotFoundError();
   }
   return Flux.fromIterable(this.handlerMappings)
       	 //根据请求地址获取对应的mapping
         .concatMap(mapping -> mapping.getHandler(exchange))
         .next()
         .switchIfEmpty(createNotFoundError())
       	 //执行目标方法
         .flatMap(handler -> invokeHandler(exchange, handler))
       	 //将返回结果封装成一个流,然后返回
         .flatMap(result -> handleResult(exchange, result));
}

先做了一个判断,如果接口映射为null,就返回一个找不到的异常,如果不为null,就用Flux将映射变成流,然后执行方法,然后把执行结果转换为flatMap返回。

ServerWebExchange:保存了HTTP请求响应信息

handlerMapping:映射信息(URL和Controller的映射信息)

DispatcherHandler:负责请求总体的处理

HandlerAdapter:适配器,负责找到具体的Controller

HandlerResultHandler:负责响应结果

SpringWebFlux实现函数式编程

实现函数式编程需要两个接口:RouterFunction和HandlerFuncition

RouterFunction:提供路由功能,将请求转发给对应的Handler

HandlerFuncition:处理请求,响应函数(执行具体的方法)

Handler中有其他的几个实现类,每个实现类都有不同的功能

image-20201107200545320

DispatcherHandler:核心控制器

ResourceWebHandler:处理静态资源的

WebHandlerDecorator:一种装饰器,装饰一些相关的扩展功能

RouterFunctionWebHandler:一些路由的处理

四、SpringWebFlux基于注解编程模型的实现

使用注解编程模型方式,和pringMVC的使用相似,只需要把相关的依赖配置到项目中就能够执行。SpringBoot会自动配置相关运行容器,默认使用Netty。

1、创建SpringBoot工程

2、引入依赖

SpringBoot版本为2.2.1.RELEASE

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

3、创建包:controller、service、dao

image-20201107205807844

4、创建一些相关的类

实体类User:

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

    public User() {
    }

    public User(String name, Integer age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
	/**
		getter And Setter...
	*/
}

用户接口UserService

/**
 * 用户操作接口
 */
public interface UserService {

    /**
     * 根据id查询用。这里用Mono。是因为我们提过,Mono是返回0或1个元素。根据id查,要么没有,要么返回1个。
     * @param id
     * @return
     */
    Mono<User> getUserById(int id);

    /**
     * 查询所有用户.用Flux是因为Flux能返回0个多个元素
     * @return
     */
    Flux<User> getAll();

    /**
     * 添加用户,不能批量添加,每次只能添加一个用户
     * @param user
     * @return
     */
    Mono<Void> saveUserInfo(Mono<User> user);
}

用户接口实现类UserServiceImpl

@Service
public class UserServiceImpl implements UserService {

    /**
     * 创建map集合存储数据
     */
    private final Map<Integer, User> users = new HashMap<Integer, User>();

    public UserServiceImpl() {
        this.users.put( 1, new User( "lucy", 26, "0" ) );
        this.users.put( 2, new User( "zhangsan", 23, "1" ) );
        this.users.put( 3, new User( "xiaohon", 18, "0" ) );
        this.users.put( 4, new User( "lisi", 28, "1" ) );
    }

    @Override
    public Mono<User> getUserById(int id) {
        return Mono.justOrEmpty( this.users.get( id ) );
    }

    @Override
    public Flux<User> getAll() {
        return Flux.fromIterable( this.users.values() );
    }

    @Override
    public Mono<Void> saveUserInfo(Mono<User> userMono) {
        return userMono.doOnNext( person -> {
            //想map集合里面取值
            int id = users.size() + 1;
            users.put( id, person );
            //清空Mono中的值,thenEmpty就是一个终止信号
        } ).thenEmpty( Mono.empty() );
    }
}

UserController

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("{id}")
    public Mono<User> getUserById(@PathVariable Integer id) {
        if (id != null && id > 0) {
            return userService.getUserById( id );
        }
        return null;
    }

    @GetMapping("/getList")
    public Flux<User> getAll() {
        return userService.getAll();
    }

    @PostMapping("/add")
    public Mono<String> addUser(@RequestBody User user) {
        if (null != user) {
            try {
                userService.saveUserInfo( Mono.just( user ) );
                return Mono.just( "用户插入成功" );
            } catch (Exception e) {
                throw new RuntimeException( "程序出错,请联系管理员" );
            }
        }
        return Mono.just( "请输入用户信息" );
    }

}

application.yml

server:
  port: 8080

5、测试

image-20201107210258090

image-20201107210227123

image-20201107210858541

五、SpringWebFlux基于函数式编程模型的实现

1、在使用函数式编程模型操作的时候,需要自己初始化服务器

2、基于函数式编程模型,有两个和兴接口:RouterFunction和HandlerFunction。核心人物定义两个函数式接口的实现并且启动需要的服务器

3、SpringWebFlux请求和响应不再是ServletRequest和ServletResponse,而是ServerRequestServerResponse

image-20201107221953179

复制一份上面的工程,删掉controller,其他的不变

新建handler包

创建Userhandler.java

public class UserHandler {

    private final UserService userService;

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

    /**
     * 根据id查询用户
     *
     * @param request
     * @return
     */
    public Mono<ServerResponse> getUserById(ServerRequest request) {
        //获取id
        int userId = Integer.valueOf( request.pathVariable( "id" ) );
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        //调用service方法得到数据
        Mono<User> userMono = this.userService.getUserById( userId );
        return userMono.flatMap( person ->
                ServerResponse.ok().contentType( MediaType.APPLICATION_JSON ).body( fromObject( person )
                        //空值判断
                ).switchIfEmpty( notFound ));
    }

    /**
     * 查询所有用户
     *
     * @return
     */
    public Mono<ServerResponse> getAllUsers(ServerRequest request) {
        Flux<User> users = this.userService.getAll();
        return ServerResponse.ok().contentType( MediaType.APPLICATION_JSON ).body( users, User.class );
    }

    /**
     * 添加用户
     * @param request
     * @return
     */
    public Mono<ServerResponse> addUser(ServerRequest request) {
        Mono<User> userMono = request.bodyToMono( User.class );

        return ServerResponse.ok().build( this.userService.saveUserInfo( userMono ) );
    }

}

注意:方法里面的参数ServerRequest request必须写

创建路由、创建服务,完成适配

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.ServerResponse;
import org.ybl.springwebfluxdemo1.handler.UserHandler;
import org.ybl.springwebfluxdemo1.service.UserService;
import org.ybl.springwebfluxdemo1.service.impl.UserServiceImpl;
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.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 RouterFunction<ServerResponse> createRouter() {
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler( userService );

        return RouterFunctions.route(
                GET( "/users/getList" ).and( accept( APPLICATION_JSON ) ), handler::getAllUsers)
                .andRoute( POST( "/users/adduser" ).and( accept( APPLICATION_JSON ) ), handler::addUser )
                .andRoute( GET( "/users/{id}" ).and( accept( APPLICATION_JSON ) ), handler::getUserById );
    }
    /**
     * 创建服务器完成适配
     */
    public void createReactorServer() {
        //完成路由和handler适配
        RouterFunction<ServerResponse> route = createRouter();
        HttpHandler httpHandler = toHttpHandler( route );

        ReactorHttpHandlerAdapter adapter
                = new ReactorHttpHandlerAdapter( httpHandler );

        //创建服务器
        HttpServer httpServer = HttpServer.create();
        //指定端口,指定适配器
        httpServer.port( 8080 ).handle( adapter ).bindNow();
    }

    
}

最终调用

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

访问

http://localhost:8080/users/

[{"name":"lucy","age":26,"sex":"0"},{"name":"zhangsan","age":23,"sex":"1"},{"name":"xiaohon","age":18,"sex":"0"},{"name":"lisi","age":28,"sex":"1"}]
posted @ 2021-10-26 22:16  程序员清风  阅读(209)  评论(0编辑  收藏  举报