zoukankan      html  css  js  c++  java
  • 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"}]
    
  • 相关阅读:
    LeetCode 189. Rotate Array
    LeetCode 965. Univalued Binary Tree
    LeetCode 111. Minimum Depth of Binary Tree
    LeetCode 104. Maximum Depth of Binary Tree
    Windows下MySQL的安装与配置
    LeetCode 58. Length of Last Word
    LeetCode 41. First Missing Positive
    LeetCode 283. Move Zeroes
    《蚂蚁金服11.11:支付宝和蚂蚁花呗的技术架构及实践》读后感
    删除docker下的镜像
  • 原文地址:https://www.cnblogs.com/lindev/p/15468161.html
Copyright © 2011-2022 走看看