Spring5 新特性

it2023-08-11  65


1. Spring 5.x 整合 Log4j2

Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2。

1.1 导入 jar 包

Log4j2 官网下载地址

1.2 创建 log4j2.xml 配置文件并进行配置

文件名必须为:log4j2.xml <?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration 后面的 status 用于设置 log4j2 自身内部的信息输出,可以不设置, 当设置成 trace 时,可以看到 log4j2 内部各种详细输出--> <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 才会生效--> <loggers> <!--root:用于指定项目的根日志,如果没有单独指定 Logger,则会使用 root 作为 默认的日志输出--> <root level="info"> <appender-ref ref="Console"/> </root> </loggers> </configuration>

1.3 测试一下 DEBUG 级别的效果

1.4 手动进行日志输出

import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Hedon Wang * @create 2020-09-16 17:17 */ public class UserLog { private static final Logger log = LoggerFactory.getLogger(UserLog.class); public static void main(String[] args) { log.info("Hello log4j2"); log.warn("Warning log4j2"); } }

2. Spring 5.x 支持 @Nullable 注解

@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空。

3. Spring 5.x 支持函数式风格(Lambda表达式)

指定 bean 名称 @Test public void testGenericApplicationContext(){ //1. 创建 GenericApplicationContext 对象 GenericApplicationContext context = new GenericApplicationContext(); //2-1 清空context context.refresh(); //2-2 注册 bean context.registerBean("account1", Account.class,() -> new Account()); //3. 获取 bean Account account1 = context.getBean("account1", Account.class); System.out.println(account1); }

不指定 bean 名称

这里不指定名称的话不是默认为“类名首字母小写”,而是需要写全限定类名来获取。

@Test public void testGenericApplicationContextWithoutName(){ //1. 创建 GenericApplicationContext 对象 GenericApplicationContext context = new GenericApplicationContext(); //2-1 清空context context.refresh(); //2-2 注册 bean context.registerBean(Account.class,() -> new Account()); //3. 获取 bean Account account1 = context.getBean("com.hedon.bean.Account", Account.class); System.out.println(account1); }

4. Spring5.x 整合 JUnit4

4.1 需要引入 spring-test 的 jar 包

4.2 使用示例

@RunWith:指定单元测试框架@ContextConfiguration:加载配置文件 @RunWith(SpringJUnit4ClassRunner.class) //指定单元测试框架 @ContextConfiguration("classpath:bean.xml") //加载配置文件 public class Junit4Test { @Autowired private AccountService accountService; @Test public void test(){ accountService.print(); } }

5. Spring 5.x 支持整合 JUnit5

5.1 引入 Junit5 的 jar 包

import org.junit.jupiter.api.Test; //然后等它报错,根据提示导入 jar 包就可以了

5.2 使用示例

//@ExtendWith(SpringExtension.class) //@ContextConfiguration("classpath:bean.xml") @SpringJUnitConfig(locations = "classpath:bean.xml") //上面两个等价于这一个 public class Junit5Test { @Autowired private AccountService accountService; @Test public void test(){ accountService.print(); } }

6. Spring WebFlux

6.1 WebFlux 简介

WebFlux 是 Spring5 添加新的模块,用于 web 开发的,功能和 SpringMVC 类似、Webflux 是当前一种比较流行的响应式编程框架。

传统 web 框架,比如 SpringMVC,这些基于 Servlet 容器,WebFlux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。

6.2 异步非阻塞

6.2.1 异步&同步

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

6.2.2 阻塞&非阻塞

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

6.3 WebFlux 特点

6.3.1 非阻塞式

在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程。

6.3.2 函数式编程

Spring5 框架基于 java8,WebFlux 使用 Java8 函数式编程方式实现路由请求。

6.4 与 Spring MVC 比较

两个框架都可以使用注解方式,都运行在 Tomet 等容器中。SpringMVC 采用命令式编程,WebFlux 采用异步响应式编程。 跨服务调用(网关)经常用 WebFlux。

6.5 响应式编程

6.5.1 介绍

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

电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。

6.5.2 Java8 及之前版本

提供的观察者模式两个类 Observer 和 Observable public class ObserverDemo extends Observable { public static void main(String[] args) { ObserverDemo observerDemo = new ObserverDemo(); //添加观察者 observerDemo.addObserver((o,arg)->{ System.out.println("发生变化"); }); //添加观察者 observerDemo.addObserver(new Observer() { @Override public void update(Observable o, Object arg) { System.out.println("收到被观察者的通知,准备发生改变"); } }); //发生变化 observerDemo.setChanged(); //通知 observerDemo.notifyObservers(); } } 输出 收到被观察者的通知,准备发生改变 发生变化 Process finished with exit code 0

6.5.3 响应式编程(Reactor 实现)

响应式编程操作中,都需要满足Reactive 规范,Reactor 就满足了。
6.5.3.1 核心类 Mono 和 Flux

Mono 和 Flux,这两个类实现接口 Publisher,提供丰富操作符。

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

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

6.5.3.2 三种信号

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

错误信号和完成信号都代表终止信号,不能共存的。终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者。

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

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

6.5.3.3 代码实现

引入 jar 包坐标

<dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> <version>3.3.6.RELEASE</version> </dependency>

编写程序代码

public class TestReactor { public static void main(String[] args) { /** * juts 方法直接申明 */ //Flux可以传多个元素 Flux.just(1,2,3,4); //Moon传1个或0个元素 Mono.just(1); /** * 其他方法 */ //数组形式的数据流 Integer[] array = {1,2,3,4}; Flux.fromArray(array); //集合形式的数据流 List<Integer> list = Arrays.asList(array); Flux.fromIterable(list); //Stream流 Stream<Integer> stream = list.stream(); Flux.fromStream(stream); } }

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

上述程序直接运行,没有东西:

15:45:29.462 [main] DEBUG reactor.util.Loggers$LoggerFactory - Using Slf4j logging framework Process finished with exit code 0

订阅一波:

//Flux可以传多个元素 Flux.just(1,2,3,4).subscribe(System.out::println); //Moon传1个或0个元素 Mono.just(1).subscribe(System.out::println);

运行程序:

15:46:23.974 [main] DEBUG reactor.util.Loggers$LoggerFactory - Using Slf4j logging framework 1 2 3 4 1 Process finished with exit code 0

6.5.4 操作符

对数据流进行一道道操作,成为操作符,比如工厂流水线。

6.5.4.1 map 元素映射为新元素
6.5.4.2 flapMap 元素映射为流
把每个元素转换流,把转换之后多个流合并大的流

6.6 WebFlux 核心容器 —— Netty

SpringWebflux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能的 NIO 框架,NIO 即同步非阻塞。

6.6.1 BIO:阻塞IO

只能同时处理一个连接,要管理多个并发客户端,需要为每个新的客户端 Socket 创建一个新的 Thread,线程模型如下图所示:

这样就会存在以下问题:

在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费。需要为每个线程的调用栈都分配内存。即使 Java 虚拟机(JVM) 在物理上可以支持非常大数量的线程, 但是远在到达该极限之前, 上下文切换所带来的开销就会带来麻烦。

6.6.2 NIO:非阻塞

从该图可以看出Selector 是Java 的非阻塞 I/O 实现的关键。它使用了事件通知 API 以确定在一组非阻塞套接字中有哪些已经就绪能够进行 I/O 相关的操作。因为可以在任何的时间检查任意的读操作或者写操作的完成状态。该种模型下,一个单一的线程便可以处理多个并发的连接。

与BIO相比,该模型有以下特点:

使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来开销。当没有 I/O 操作需要处理的时候,线程也可以被用于其他任务。

虽然Java 的NIO在性能上比BIO已经相当的优秀,但是要做到如此正确和安全并不容易。特别是,在高负载下可靠和高效地处理和调度 I/O 操作是一项繁琐而且容易出错的任务,于是就有了 Netty。

6.6.3 Netty

Netty对NIO的API进行了封装,通过以下手段让性能又得到了一定程度的提升:

使用多路复用技术,提高处理连接的并发性。零拷贝。Netty的接收和发送数据采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象进行一次操作。Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。内存池:为了减少堆外直接内存的分配和回收产生的资源损耗问题,Netty提供了基于内存池的缓冲区重用机制。使用主从Reactor多线程模型,提高并发性。采用了串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降。默认使用Protobuf的序列化框架。灵活的TCP参数配置。

参考1:https://blog.csdn.net/eric_sunah/article/details/80424344

参考2:https://www.infoq.cn/article/netty-high-performance/#anch111813

6.7 WebFlux 核心控制器

SpringWebflux 核心控制器是 DispatchHandler,该控制器负责请求的处理。它实现了接口 WebHandler,WebHandler 中只有一个方法 handler(),它在 DispatchHandler 中的实现如下::

public Mono<Void> handle(ServerWebExchange exchange) { //ServerWebExchange:存放http请求响应的信息 //如果 handlerMappings 为空,则返回一个 createNotFoundError 的错误 return this.handlerMappings == null ? this.createNotFoundError() : //如果不为空,则根据请求地址获取对应的 mapping Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> { return mapping.getHandler(exchange); }).next().switchIfEmpty(this.createNotFoundError()).flatMap((handler) -> { //调用具体的业务方法 return this.invokeHandler(exchange, handler); }).flatMap((result) -> { //返回处理结果 return this.handleResult(exchange, result); }); }

DispatchHandler 有 3 个属性:

public class DispatcherHandler implements WebHandler, ApplicationContextAware { @Nullable private List<HandlerMapping> handlerMappings; @Nullable private List<HandlerAdapter> handlerAdapters; @Nullable private List<HandlerResultHandler> resultHandlers; HandlerMapping:请求查询到处理的方法HandlerAdapter:真正负责请求处理HandlerResultHandler:响应结果处理

6.8 WebFlux 实现函数式的两个重要接口

RouterFunction:路由处理HandlerFunction:处理函数

6.9 WebFlux 基于注解编程模型

6.9.1 创建 SpringBoot 项目,引入 WebFlux 依赖

将:

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

换成:

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

6.9.2 配置启动端口号

server.port=8081

6.9.3 创建包和相关类

实体类 User

public class User { private String name; private String gender; private Integer age; //getter、setter //有参、无参构造 //toString() }

创建接口 UserService,定义操作方法

public interface UserService { //根据id查询用户 Mono<User> getUserById(Integer id); //查询所有用户 Flux<User> getAllUsers(); //添加用户 Mono<Void> saveUserInfo(Mono<User> userMono); }

创建接口实现类 UserServiceImpl,实现具体操作:

@Service public class UserServiceImpl implements UserService { //创建 map 集合存储数据 private final Map<Integer, User> users = new HashMap<>(); //初始化 public UserServiceImpl() { this.users.put(1, new User("lucy", "男", 20)); this.users.put(2, new User("mary", "女", 30)); this.users.put(3, new User("jack", "女", 50)); } //根据ID查询User @Override public Mono<User> getUserById(Integer id) { return Mono.justOrEmpty(this.users.get(id)); } //查询所有User @Override public Flux<User> getAllUsers() { return Flux.fromIterable(this.users.values()); } //保存 User @Override public Mono<Void> saveUserInfo(Mono<User> userMono) { return userMono.doOnNext(user -> { //向 map 中放值 int id = users.size()+1; users.put(id,user); }).thenEmpty(Mono.empty()); //操作后将 Mono 中的值清空(即发送终止信号) } }

6.9.4 创建控制器

@RestController public class UserController { //注入 userService @Autowired private UserService userService; //id 查询 @GetMapping("/user/{id}") public Mono<User> getUserById(@PathVariable int id) { return userService.getUserById(id); } //查询所有 @GetMapping("/user") public Flux<User> getUsers() { return userService.getAllUsers(); } //添加 @PostMapping("/saveUser") public Mono<Void> saveUser(@RequestBody User user) { Mono<User> userMono = Mono.just(user); return userService.saveUserInfo(userMono); } }

6.9.5 测试

6.9.6 比较 SpringMVC

SpringMVC 方式实现,同步阻塞的方式,基于 SpringMVC+Servlet+Tomcat。SpringWebFlux 方式实现,异步非阻塞方式,基于 SpringWebFlux+Reactor+Netty。

6.10 WebFlux 基于函数式编程模型

6.10.1 注意点

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

6.10.2 复制基于注解编程模型的工程,保留实体类和服务层

6.10.3 创建 Handler(编写业务具体实现方法)

public class UserHandler { private final UserService userService; public UserHandler(UserService userService) { this.userService = userService; } //根据 id 查询 public Mono<ServerResponse> getUserById(ServerRequest serverRequest){ //1. 获取 id 值 Integer id = Integer.valueOf(serverRequest.pathVariable("id")); //2. 空值处理 Mono<ServerResponse> notFound = ServerResponse.notFound().build(); //3. 调用 service 方法得到数据 Mono<User> userMono = this.userService.getUserById(id); //4. 把 userMono 进行转换返回 => 使用 Reactor 操作符 flatMap return userMono.flatMap(user -> ServerResponse .ok() .contentType(MediaType.APPLICATION_JSON) .body(fromObject(user)) .switchIfEmpty(notFound)); } //查询所有 public Mono<ServerResponse> getAllUsers(ServerRequest serverRequest){ //调用 service 得到结果 Flux<User> users = this.userService.getAllUsers(); return ServerResponse .ok() .contentType(MediaType.APPLICATION_JSON) .body(users,User.class); } //添加 public Mono<ServerResponse> addUser(ServerRequest serverRequest){ //得到 user 对象 Mono<User> userMono = serverRequest.bodyToMono(User.class); //添加 return ServerResponse .ok() .build(this.userService.saveUserInfo(userMono)); } }

6.10.4 初始化服务器,编写 Router

public class Server { /** * ① 创建 Router 路由 */ public RouterFunction<ServerResponse> routingFunction() { //1. 创建 hanler 对象 UserService userService = new UserServiceImpl(); UserHandler handler = new UserHandler(userService); //2. 设置路由 return RouterFunctions.route( /** * 请求的地址 * 接收的参数 * 调用的方法 */ GET("/user/{id}") .and(accept(APPLICATION_JSON)), handler::getUserById) .andRoute( GET("/users") .and(accept(APPLICATION_JSON)), handler::getAllUsers ); } /** * ② 创建服务器完成适配 */ public void createReactorServer() { //1. 路由和 handler 适配 RouterFunction<ServerResponse> route = routingFunction(); HttpHandler httpHandler = toHttpHandler(route); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); //2. 创建服务器 HttpServer httpServer = HttpServer.create(); httpServer.handle(adapter).bindNow(); } /** * ③ 最终调用 */ public static void main(String[] args) throws Exception { Server server = new Server(); server.createReactorServer(); //创建服务器 System.out.println("enter to exit"); //按回车键退出 System.in.read(); } }

6.10.5 运行 main() 方法进行测试

访问:http://localhost:49733/users

6.10.6 使用 WebClient 调用上面的接口

public class Client { public static void main(String[] args) { //调用服务器地址 WebClient client = WebClient.create("http://localhost:49733"); //1. 根据 id 查询 String id = "1"; User userResult = client.get().uri("/user/{id}", id) .accept(MediaType.APPLICATION_JSON) .retrieve() //检索(储存的信息) .bodyToMono(User.class) //Mono返回1个或0个元素 .block(); //终止信号 //输出查询到的User System.out.println(userResult); //2. 查询所有 Flux<User> userFlux = client.get().uri("/users") .accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToFlux(User.class); //Flux返回多个元素 //输出 userFlux.map(user -> user.getName()) //map即前面说的把一个元素映射为另外一个元素 .buffer() .doOnNext(System.out::println) .blockFirst(); //终止信号 } }

运行:

最新回复(0)