Java内存马之WebFlux基础知识补充

Spring WebFlux

一文弄懂 Spring WebFlux 的来龙去脉

Spring WebFluxSpring Framework 5.0中引入的一个新的响应式框架,旨在为在Spring中构建响应式应用程序提供支持。Spring WebFlux基于Reactor项目,是一个异步、非阻塞的框架,专门用于处理并发性较高且需要非阻塞I/O操作的应用程序,例如实时数据处理、高性能API服务等。与传统的Spring MVC不同,WebFlux旨在充分利用多核心和现代硬件的优势,提高应用程序的吞吐量和伸缩性。
该框架队接口的返回类型进行控制,使用Mono<T>Flux<T>,这里简单介绍一下什么是MonoFlux

什么是 Mono

Mono代表的是一个或零个元素的异步序列。你可以将其视作响应式编程中的Future或Optional,但提供了更丰富的操作和组合性。Mono是用来处理异步任务的,尤其是那些最多只返回单个值的操作,例如一个数据库查询可能返回零个或一个结果。

1
2
Mono<String> noData = Mono.empty(); // 表示空的异步序列
Mono<String> data = Mono.just("Some data"); // 表示单个值的异步序列

什么是 Flux

Flux代表的是零到多个元素的异步序列。Flux可以发出任意数量的数据项。它用于表示可能包含多个值的异步操作,例如获取所有用户的列表或从数据流中获取数据。

1
2
Flux<String> emptyFlux = Flux.empty(); // 空的数据流
Flux<String> dataFlux = Flux.just("Data1", "Data2", "Data3"); // 包含多个数据项的数据流

Spring WebFlux 启动过程分析

使用之前的项目,按照要求在run这里下断点。
image.png
直接跳到org.springframework.boot.SpringApplication#createApplicationContext这里。
image.png
创建上下文工厂类管理,再进。
image.png
根据 webapplicationType创建context,进入 create 方法:
image.png
这个工厂类的create方法根据传入的WebApplicationType参数来决定是否创建一个新的AnnotationConfigReactiveWebServerApplicationContext实例。如果传入的webApplicationType不是REACTIVE,则方法返回null,表示这个工厂仅支持反应式Web应用。如果是REACTIVE,则实例化并返回一个新的AnnotationConfigReactiveWebServerApplicationContext对象。
继续看下去,能注意到REACTIVE 所对应的是org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
image.png
步过出这个方法,能回到run方法。
image.png
分别调用prepareContextrefreshContextafterRefresh
image.png
进到refreshContext其中,再进refresh,再进refresh。发现调用父类的refresh,进入看看:
image.png
image.png
进入到onRefresh,跳到了org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext#onRefresh
能在 org.springframework.boot.web.reactive.context.WebServerManager 中看到有三个常量:
image.png

  • applicationContext:这是ReactiveWebServerApplicationContext类型的一个实例变量,它在WebServerManager的构造函数中通过参数传入,并在整个WebServerManager类中被使用。这个上下文对象代表了Spring应用的反应式Web应用上下文,用于管理Spring Bean的生命周期和提供配置信息。
  • webServer:这是WebServer类型的一个实例变量,它在WebServerManager的构造函数中通过ReactiveWebServerFactorygetWebServer方法初始化。webServer代表了应用的Web服务器,负责处理HTTP请求和响应。WebServerManager类中的startstopshutDownGracefully方法都是围绕这个webServer实例进行操作的。
  • handler:这是DelayedInitializationHttpHandler类型的一个实例变量,它也是在WebServerManager的构造函数中初始化的。这个handler对象是一个HTTP处理器,负责处理进入Web服务器的HTTP请求。它的初始化可能会延迟,直到第一个HTTP请求到达,这取决于lazyInit标志的值。

而启动 webserver 的位置在finishRefresh
image.png
image.png
进入onRefresh方法,调用了 StartBeans 方法。
image.png
这里开启了 autoStartUpOnly根据字面意思能知道是自启动,然后获取生命周期bean:通过调用getLifecycleBeans获取所有实现了Lifecycle接口的bean。这些bean可能包括SmartLifecycle和普通的Lifecycle实现。
然后再分组,方便后面低级和高级按序进行操作。然后就是 start 方法启动,并给 phases 赋值。
image.png
检测 Debug,排序,再进入 doStart 方法。再进到 start 方法:
image.png
调用this.handler.initializeHandler()来初始化HttpHandler。这一步非常重要,因为HttpHandler负责处理HTTP请求。如果在创建WebServerManager时设置了lazyInit为true,那么处理器的实际初始化将延迟到收到第一个HTTP请求时。再启动 web 服务器,最后发布一个 Event,这个事件表明web服务器已经完全初始化并准备就绪。
再进start方法,会进到:org.springframework.boot.web.embedded.netty.NettyWebServer#start
image.png
直接就能看出来是靠org.springframework.boot.web.embedded.netty.NettyWebServer#startHttpServer方法执行起webServer服务。

Spring WebFlux 请求处理过程分析

我们回到最开始处理的com.natro92.webfluxdemo.hello.GreetingHandler#hello打上断点:
image.png
访问/hello路由,然后步过一次,进到org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter#handle
image.png
一直步过直到找到 invokeHandler 方法:
image.png
进入到 handle 方法中
image.png
眼熟,分析下这段代码:

  • 检查 handler 映射。
  • 检测是否是 CORS 的预请求,调用其他请求方法。
  • 然后就是这一大堆 return 的内容,创建一个 Flux 流,遍历 handlerMappings,然后 concatMap 字面意思就是拼接,然后next选择第一个匹配的处理器,如果是空,则返回一个 NotFoundError(这里应该指的是 404)。后面的 flatMap 里面处理 handler,再将结果返回。

这里的调用栈中有一个org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler
image.png
我们回到刚才的调用栈分析,这个方法在org.springframework.web.reactive.function.server.support.RouterFunctionMapping#getHandlerInternal中呗继承。
Clip_2024-07-15_17-36-26.png
点进 create 方法中:
Clip_2024-07-15_17-24-24.png
里面创建了 org.springframework.web.reactive.function.server.DefaultServerRequest对象,传入的参数有:HttpMessageReader,可以提供解析参数的能力。
回到 org.springframework.web.reactive.function.server.support.RouterFunctionMapping#getHandlerInternal方法中,发现调用了 route 函数
Clip_2024-07-15_17-28-50.png
Clip_2024-07-15_17-29-13.png
根据意思返回匹配请求的 handler 函数
Clip_2024-07-15_17-41-52.png
this.predicate.test检查了请求是否符合路由要求,如果匹配到了处理方法,就返回保存的 handlerFunction,如果没有就返回空的 Mono
再进去 test 方法,是个接口。根据前面的文件命名方法,应该在RequestPredicates中。
Clip_2024-07-15_17-52-57.png
断点调试。这里已经拿到了 pattern 信息
Clip_2024-07-15_17-57-47.png
Clip_2024-07-15_17-59-24.png
leftright 分别赋值。这里是调用了org.springframework.web.reactive.function.server.RequestPredicates.AndRequestPredicate#AndRequestPredicate的方法,如下。
Clip_2024-07-15_18-03-09.png
而我们可以发现,如果给这里断点的话,不需要访问路由就可以到这里,就说明这里是预先就进行了准备。
然后我们再回到org.springframework.web.reactive.DispatcherHandler#invokeHandler的断点。
Clip_2024-07-15_18-08-50.png
能注意到这里的this.handlerAdapters已经有了四个值。
并不是所有的handlerAdapter都会触发handle方法,只有当支持我们给定的handlerhandlerAdapter才可以调用。

WebFilter 过程分析

对于 Spring WebFlux 而言,没有拦截器和监听器。需要对权限验证和访问控制,需要用Filter
通过实现org.springframework.web.server.WebFilter接口,来定义全局的过滤器,在路由到handler的前后执行逻辑;或者通过是实现HandlerFilterFunction也可以。
新建一个GreetingFilter.java类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.natro92.webfluxdemo.hello;

import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Mono;

@Component
public class GreetingFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
PathPattern pattern=new PathPatternParser().parse("/hello/**");
ServerHttpRequest request=serverWebExchange.getRequest();
if (pattern.matches(request.getPath().pathWithinApplication())){
System.out.println("hello, this is our filter!");
}
return webFilterChain.filter(serverWebExchange);
}
}

Clip_2024-07-27_14-53-15.png
我们在这个 filter 方法处下断点。
Clip_2024-07-27_14-59-16.png
进入 return 里的 filter 函数。
Clip_2024-07-27_15-04-01.png
defer 方法延迟执行,判断过滤器和链子是不是空,如果是空则调用 filter 方法,进行实现,如果不为空,那个直接给 handler 的 handle 方法实现。
进入 org.springframework.web.server.handler.DefaultWebFilterChain#invokeFilter方法中
而这个类有三个构造方法,一个是公共的构造函数,一个是私有构造,还有个一个已被弃用的。
Clip_2024-07-27_15-23-36.png
其中注释中写了,我最开始以为是代表着一个对 chain 的连接,后来看了网上的说法是代表的链中的 link。无法通过修改chain.allFilters实现新增Filter
Clip_2024-07-27_15-27-46.png
这里这个 allFilters存储着我们之前编写的Filter
这个类中有个initChain方法通过实例化来调用了这个类,而且使用的是注释中说明的私有化调用。
Clip_2024-07-27_15-30-00.png
我们找一下公共调用Ctrl + Alt + F7
Clip_2024-07-27_15-43-58.png
进去第四个也就是在org.springframework.web.server.handler.FilteringWebHandler#FilteringWebHandler调用的。
Clip_2024-07-27_15-52-45.png
也就是说我们可以构造一个DefaultWebFilterChain对象,通过反射来写入到FilteringWebHandlerchain 属性。
然后需要搞清楚handlerfilters这两个参数。而 handler 这个参数已经在 chain 中。
Clip_2024-07-27_16-05-10.png
这个 filters,可以通过先获取到原本的 filters,然后再把原先的恶意filter放进去。放到第一个就可以了。
然后就是再内存中找到DefaultWebFilterChain,然后反射就行。
这里使用这个工具:

https://github.com/c0ny1/java-object-searcher

先 clone 下来,再放到 idea 中,mvn clean install
这里我遇到点问题,用 8 环境把编译版本改了还一直报错找不到一个类,直接在 cmd 临时用 7 编译一下就好了。

1
2
set JAVA_HOME=D:/java/jdk1.8
mvn clean package

然后添加进库就行。
Clip_2024-07-27_17-04-44.png
然后修改GreetingFilter.java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.natro92.webfluxdemo.hello;

import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Mono;

import me.gv7.tools.josearcher.entity.Blacklist;
import me.gv7.tools.josearcher.entity.Keyword;
import me.gv7.tools.josearcher.searcher.SearchRequstByBFS;
import java.util.ArrayList;
import java.util.List;

@Component
public class GreetingFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
PathPattern pattern=new PathPatternParser().parse("/hello/**");
ServerHttpRequest request=serverWebExchange.getRequest();
if (pattern.matches(request.getPath().pathWithinApplication())){
System.out.println("hello, this is our GreetingFilter!");
}
List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("DefaultWebFilterChain").build());
List<Blacklist> blacklists = new ArrayList<>();
blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
searcher.setBlacklists(blacklists);
searcher.setIs_debug(true);
searcher.setMax_search_depth(10);
// 改成你自己的位置
searcher.setReport_save_path("D:\\Wo****\\apache-tomcat-9.0.83\\bin");
searcher.searchObject();
return webFilterChain.filter(serverWebExchange);
}
}

但是我这里运行出的结果没找到含关键词的这个。。。

1
2
3
4
5
6
7
8
9
10
11
TargetObject = {reactor.netty.resources.DefaultLoopResources$EventLoop} 
---> group = {java.lang.ThreadGroup}
---> threads = {class [Ljava.lang.Thread;}
---> [3] = {org.springframework.boot.web.embedded.netty.NettyWebServer$1}
---> this$0 = {org.springframework.boot.web.embedded.netty.NettyWebServer}
---> handler = {org.springframework.http.server.reactive.ReactorHttpHandlerAdapter}
---> httpHandler = {org.springframework.boot.web.reactive.context.WebServerManager$DelayedInitializationHttpHandler}
---> delegate = {org.springframework.web.server.adapter.HttpWebHandlerAdapter}
---> delegate = {org.springframework.web.server.handler.ExceptionHandlingWebHandler}
---> delegate = {org.springframework.web.server.handler.FilteringWebHandler}
---> chain = {org.springframework.web.server.handler.DefaultWebFilterChain}