• 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:ideaprojectSpring5	argetclasseslog4j2.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:ideaprojectSpring5	argetclasseslog4j2.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:ideaprojectSpring5	argetclasseslog4j2.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:ideaprojectSpring5	argetclasseslog4j2.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:ideaprojectSpring5	argetclasseslog4j2.xml] initialized
    2020-11-07 02:47:40,972 main DEBUG Starting configuration XmlConfiguration[location=F:ideaprojectSpring5	argetclasseslog4j2.xml]
    2020-11-07 02:47:40,972 main DEBUG Started configuration XmlConfiguration[location=F:ideaprojectSpring5	argetclasseslog4j2.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:ideaprojectSpring5	argetclasseslog4j2.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 FilesJavajdk1.8.0_251injava.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("
    =========");
            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("
    =========");
    
            List<Integer> list = Arrays.asList( array );
            Flux.fromIterable( list ).subscribe(System.out::print);
            System.out.println("
    =========");
    
            Stream<Integer> stream = list.stream();
            Flux.fromStream( stream ).subscribe(System.out::print);
            System.out.println("
    =========");
            //错误信号
            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"}]
    
  • 相关阅读:
    java springboot连接两个数据源
    Android状态栏高度、虚拟操作栏高度
    Java代码实现两种数据库的数据迁移
    微信小程序生成海报
    【比较耗性能的一个东西】Firefox的LayoutReflow
    JS中关于带操作赋值的一个小问题
    js控制样式一个细节
    javascript中打开客户端,关于void的一个疑问
    项目管理日志05-01-05
    项目管理日志05-01-04
  • 原文地址:https://www.cnblogs.com/lindev/p/15468161.html
Copyright © 2020-2023  润新知