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(下面讲怎么整合)
4、Spring5核心容器支持函数式风格GenericApplicationContext/AnnotationConfigApplicationContext
6.1、[Spring WebFlux介绍](#一、Spring WebFlux介绍)
6.2、响应式编程
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
属性:表示属性值可以为null
参数:表示参数值可以为null
org.springframework.context.annotation.AnnotationConfigApplicationContext
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的对比
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 元素映射成新的元素
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中有其他的几个实现类,每个实现类都有不同的功能
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
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、测试
五、SpringWebFlux基于函数式编程模型的实现
1、在使用函数式编程模型操作的时候,需要自己初始化服务器
2、基于函数式编程模型,有两个和兴接口:RouterFunction和HandlerFunction。核心人物定义两个函数式接口的实现并且启动需要的服务器
3、SpringWebFlux请求和响应不再是ServletRequest和ServletResponse,而是ServerRequest
和ServerResponse
复制一份上面的工程,删掉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();
}
访问
[{"name":"lucy","age":26,"sex":"0"},{"name":"zhangsan","age":23,"sex":"1"},{"name":"xiaohon","age":18,"sex":"0"},{"name":"lisi","age":28,"sex":"1"}]