Reactor Netty (一)netty服务器配置启动流程封装设计源码解读

本文深入探讨了ReactorNetty在SpringCloudGateway中的作用,作为WebFlux和Netty之间的桥接层,支持函数式编程。通过分析HttpServer的创建和配置过程,展示了如何利用链式编程实现延迟加载,以及在启动服务器时如何逐步应用配置。文章以代码示例解释了配置方法如host()、port()的内部工作原理,强调了函数式编程在简化调试和提高可读性方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

reactor-netty 简介

  • 在SpringCloud微服务体系中,有个很重要的组件就是网关,在2.x版本中SpringCloud自己研发了一个网关替代Zuul,那就是SpringCloud Gateway,而Gateway是支持WebFlux的函数式(流式)编程的,WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在Netty上,并且SpringCloud Gateway 默认容器就是netty,但是netty本身并不支持函数式编程,所以就有了我们今天讲解的主角Reactor Netty,当netty作为服务器时,就是基于Reactor Netty来进行函数式支持,也就是说Reactor Netty是WebFlux和Netty之间的桥接层。
  • Reactor Netty提供基于Netty框架的无阻塞和回压的tcp/udp 客户端和服务器,它主要提供了易于配置的HttpServer类,隐藏了创建HTTP服务器所需的大部分Netty功能
  • 如下图所示,Reactor Netty处于Reactive Streams 这一层,向下兼容netty,向上支持webflux
    在这里插入图片描述

接下来就看看Reactor Netty 是如何支持函数式编程的

HttpServer 配置设计源码分析

HttpServer

在Reactor Netty中只需要以下几行代码就可以创建一个支持函数式的netty服务器

HttpServer.create()
          .host("0.0.0.0")
          .port(8080)
          .handle((req, res) -> res.sendString(Flux.just("hello")))
          .bind()
          .block();
}
  • 说到函数式编程,必然会涉及到链式编程,在链式编程中为了降低debug难度和提高可读性,一般都会在每次调用完一个函数后返回相同的对象,上面的代码中每一个函数调用完返回的对象都是HttpServer的实例
  • 同时,函数式编程一般都有个特性就是延迟加载,在上面的代码中HttpServer 每次调用的函数例如host()、port()、handle()、并没有真正执行这个配置,直到最后调用bind()才真正将之前的配置组装到HttpServer的实例中

打开HttpServer#create()方法可以看到返回了HttpServerBind的一个实例

public static HttpServer create() {
	return HttpServerBind.INSTANCE;
}

而HttpServerBind继承了HttpServer这个抽象类,并且实现了它的两个抽象方法

final class HttpServerBind extends HttpServer
		implements Function<ServerBootstrap, ServerBootstrap>{
		    @Override
	protected TcpServer tcpConfiguration() {
		return tcpServer;
	}

	@Override
	public Mono<? extends DisposableServer> bind(TcpServer delegate) {
		return delegate.bootstrap(this)
		               .bind()
		               .map(CLEANUP_GLOBAL_RESOURCE);
	}
}

接着查看HttpServer#host()方法,可以发现连同port()、forwarded()等方法他们的代码逻辑都是一样的,都会调用HttpServer的tcpConfiguration()方法,并且传入一个函数对象(Function<? super TcpServer, ? extends TcpServer> tcpMapper),这个对象本身代表着要执行的一段代码逻辑,用来在server真正启动加载配置的时候去执行,最后类似host()的这些的配置方法(后文都会用配置方法来指代port()、forwarded()这些执行逻辑类似的方法)都会返回HttpServer这个对象

public final HttpServer host(String host) {
		return tcpConfiguration(tcpServer -> tcpServer.host(host));
	}
public final HttpServer tcpConfiguration(Function<? super TcpServer, ? extends TcpServer> tcpMapper) {
		return new HttpServerTcpConfig(this, tcpMapper);
	}

HttpServer#tcpConfiguration这个方法中都会新创建一个HttpServerTcpConfig对象来代表当前添加的配置

final class HttpServerTcpConfig extends HttpServerOperator {

	final Function<? super TcpServer, ? extends TcpServer> tcpServerMapper;

	HttpServerTcpConfig(HttpServer server,
			Function<? super TcpServer, ? extends TcpServer> tcpServerMapper) {
		super(server);
		this.tcpServerMapper = Objects.requireNonNull(tcpServerMapper, "tcpServerMapper");
	}

	@Override
	protected TcpServer tcpConfiguration() {
		return Objects.requireNonNull(tcpServerMapper.apply(source.tcpConfiguration()),
				"tcpServerMapper");
	}
}

HttpServerTcpConfig 继承了HttpServerOperator 这个抽象类,而HttpServerOperator继承了HttpServer

abstract class HttpServerOperator extends HttpServer {

	final HttpServer source;

	HttpServerOperator(HttpServer source) {
		this.source = Objects.requireNonNull(source, "source");
	}

	@Override
	protected TcpServer tcpConfiguration() {
		return source.tcpConfiguration();
	}

	@Override
	protected Mono<? extends DisposableServer> bind(TcpServer b) {
		return source.bind(b);
	}
}

也就是说调用配置方法host(),会返回一个HttpServerTcpConfig对象,它代表了HttpServer中tcp的一个配置,所以在它的构造函数中会保存这个配置逻辑对象Function<? super TcpServer, ? extends TcpServer> tcpServerMapper

HttpServerTcpConfig(HttpServer server,
			Function<? super TcpServer, ? extends TcpServer> tcpServerMapper) {
		super(server);
		this.tcpServerMapper = Objects.requireNonNull(tcpServerMapper, "tcpServerMapper");
}

这个对象的执行逻辑在host()中就是用来设置tcp的host地址的,它的逻辑就是

tcpServer -> tcpServer.host(host)

,在port()中就是用来设置tcp的端口的,他的逻辑就是

tcpServer -> tcpServer.port(port)

同时HttpServerTcpConfig也是一个HttpServerOperator,它代表了一个操作对象,这个操作对象就代表着一个配置函数,它的构造函数中记录了它的上一个函数操作source

HttpServerOperator(HttpServer source) {
		this.source = Objects.requireNonNull(source, "source");
}
  • 看到这里,可能疑问会越来越多,为什么每个配置方法都会返回创建一个HttpServerTcpConfig?为什么HttpServerTcpConfig会继承HttpServerOperator?为什么HttpServerOperator会集成HttpServer?为什么HttpServerTcpConfig要保存配置逻辑?为什么HttpServerOperator要保存上一步的配置函数?等等问题,不要着急,请带着这些问题接着往后看

  • 为了简化对问题的讲解,这里假设我们的HttpServer只需要配置host()、port()之后就可以启动服务器了,实际上其他的配置逻辑类似handle()等也是一样的,明白一个,其他的也就是明白了,一通百通

  • 的HttpServer要启动服务器最后还需要调用bind()方法

public final Mono<? extends DisposableServer> bind() {
		return bind(tcpConfiguration());
}

可以看到bind方法会掉用tcpConfiguration()这个方法,但是他并不是直接调用的HttpServer中的tcpConfiguration()方法,回想一下HttpServer的创建流程,会先调用host(),再调用port(),前面有说过每次调用这样的配置方法,都会新创建一个HttpServerTcpConfig对象返回,所以在调用bind() 之前,当前的调用者是配置port对应的那个HttpServerTcpConfig类型的对象,由于java的多态动态绑定,这里调用的tcpConfiguration()方法就是HttpServerTcpConfig实现的tcpConfiguration()

@Override
protected TcpServer tcpConfiguration() {
	return Objects.requireNonNull(tcpServerMapper.apply(source.tcpConfiguration()),
			"tcpServerMapper");
}

这个方法中首先会接着调用source的tcpConfiguration(),而source 在这里是什么呢?

由于我们之前假设只依次调用了两个配置方法host()、port() ,所以这里的source应该逆序对应的是配置host对应的HttpServerTcpConfig类型的对象,它也会执行相同的逻辑source.tcpConfiguration(),这个时候这个source又是什么呢?

再次会看一下服务器的创建流程,首先会调用create()方法,它会返回一个HttpServerBind类型的对象,所以source就是HttpServerBind对象,它会调用自己的tcpConfiguration()方法

@Override
protected TcpServer tcpConfiguration() {
	return tcpServer;
}

这个时候返回的才是服务器对象tcpserver。
由于我们专注的是讲解http层面的配置启动逻辑,所以对于tcpserver不做过多讲解,但是tcpserver支持实现函数式也是一样的道理,这里不在赘述

tcpConfiguration()的嵌套调用过程可以看下图帮助你理解

在这里插入图片描述

接着回看HttpServerTcpConfig的tcpConfiguration()方法,一步步嵌套调用到最底层获取到tcpserver之后,再逆向一步步调用tcpServerMapper.apply(),这个时候才是之前添加的配置函数真正执行的时候,也就是说会依次执行

tcpServer.host(host)
tcpServer.port(port)

tcpConfiguration()中cpServerMapper.apply()的调用顺序过程可以看下图帮助你理解
在这里插入图片描述
当tcpConfiguration()执行完之后又回到了HttpServer的bind()

public final Mono<? extends DisposableServer> bind() {
		return bind(tcpConfiguration());
}

接着会执行bind()函数,由于在HttpServer中这是一个抽象函数

protected abstract Mono<? extends DisposableServer> bind(TcpServer b);

所以肯定要执行实现类的bind(),之前说过当前的调用者对象是HttpServerTcpConfig类型的,同时也是HttpServerOperator类型的,所以会调用HttpServerOperator的bind()

@Override
protected Mono<? extends DisposableServer> bind(TcpServer b) {
	return source.bind(b);
}

有了上一次的分析思路,这里的source很容易就可以分析出来到底是什么类型的对象,首先是HttpServerOperator,最后是HttpServerBind,
再看一下HttpServerBind的bind调用逻辑,封装的是对tcpserver的启动逻辑,类似于httpserver的启动逻辑,参数是TcpServer,这个对象是之前添加了我们配置了host和port的TcpServer,然后将它交给下一层去启动服务器,最后再将启动好的服务器层层返回到我们的http层,关于tcp层的启动流程这里不再赘述

@Override
public Mono<? extends DisposableServer> bind(TcpServer delegate) {
	return delegate.bootstrap(this)
	               .bind()
	               .map(CLEANUP_GLOBAL_RESOURCE);
}

学以致用

接下来我们要通过自己的理解来构造一个支持函数式的httpserver,以便我们加深对其设计的理解,首先创建HttpServer的抽象类,他是我们最终启动http服务器的主体,他需要支持创建服务器实例对象,所以需要有create(),同时他还要支持设置host、port、制定是否压缩、以及重定向,所以需要有port(int port)、host(String host) 、compress(String compress)、forwarded(String forwardedEnabled),添加完配置后,最后我们还需要bind()来启动我们的服务器


public abstract class HttpServer {
    public static HttpServer create() {
        return HttpServerBind.DEFAULT_HTTP_SERVER;
    }
    static final HttpServer DEFAULT_HTTP_SERVER = HttpServerBind.create();

    public final HttpServer tcpConfiguration(Function<? super HttpServer, ? extends HttpServer> httpMapper,String name) {
        return new HttpServerTcpConfig(this, httpMapper,name);
    }
    protected HttpServer tcpConfiguration() {
        return DEFAULT_HTTP_SERVER;
    }
    public final Mono bind() {
        System.out.println("http server bind()");
        return bind(tcpConfiguration());
    }
    protected abstract Mono bind(HttpServer b);

    public final HttpServer setHost(String host){
        System.out.println("httpserver set host :" + host);
        return this;
    }
    public final HttpServer setCompress(String compress){
        System.out.println("httpserver set compress :" + compress);
        return this;
    }

    public final HttpServer setPort(int port){
        System.out.println("httpserver set port :" + port);
        return this;
    }
    public final HttpServer setForwarded(String forwarded){
        System.out.println("httpserver set forwarded :" + forwarded);
        return this;
    }

    public final HttpServer port(int port){
        return tcpConfiguration(httpServer -> httpServer.setPort(port),"port");
    }
    public final HttpServer host(String host) {
        return tcpConfiguration(httpServer -> httpServer.setHost(host),"host");
    }

    public final HttpServer compress(String compress) {
        return tcpConfiguration(httpServer -> httpServer.setCompress(compress),"compress");
    }
    public final HttpServer forwarded(String forwardedEnabled) {
        return tcpConfiguration(httpServer -> httpServer.setForwarded(forwardedEnabled),"forwarded");
    }
}

为了支持链式编程,同时保持每次返回的都是我们的HttpServer类型的对象,所以HttpServer#create()会返回一个HttpServer类型的对象,同时它还需要支持一个很重的功能就是,最后来启动我们的服务器,所以需要继承HttpServer来实现bind()

final class HttpServerBind extends HttpServer {
    static final HttpServerBind DEFAULT_HTTP_SERVER = new HttpServerBind();
    HttpServerBind() {
    }
    public static HttpServer create() {
        System.out.println("create http server instance");
        return DEFAULT_HTTP_SERVER;
    }
    @Override
    public Mono bind(HttpServer delegate) {
        System.out.println("http server bind finish");
        return null;
    }
    @Override
	protected HttpServer tcpConfiguration() {
		return DEFAULT_HTTP_SERVER;
	}
}

创建完HttpServer的实例对象HttpServerBind,接下来要为服务器添加配置,这里不是真正将配置作用于我们的服务器,而是将添加配置的逻辑操作暂存起来,当最后调用bind()才真正组装服务器,所以我们需要一个操作类型的对象(HttpServerOperator)来暂时存储每添加一个配置是对应的逻辑操作(tcpServerMapper),同时这个对象(HttpServerTcpConfig)还要能够记录下它的上一步配置操作(source),所以HttpServerOperator作为一个操作对象记录了它的上一步操作,HttpServerTcpConfig作为配置对象,记录了当前配置的执行逻辑

abstract class HttpServerOperator extends HttpServer {
    final HttpServer source;
    String name ;
    HttpServerOperator(HttpServer source,String name) {
        this.source = Objects.requireNonNull(source, "source");
        this.name = "HttpServerOperator " + name;
    }

    @Override
    protected HttpServer tcpConfiguration() {
        System.out.println(this.name + " ----tcpConfiguration()");
        return source.tcpConfiguration();
        System.out.println(String.format("save source %s",this.name));
    }

    @Override
    protected Mono bind(HttpServer b) {
        System.out.println(this.name + " bind()");
        return source.bind(b);
    }
}

public class HttpServerTcpConfig extends HttpServerOperator {
    final Function<? super HttpServer, ? extends HttpServer> tcpServerMapper;
    String name ;
    HttpServerTcpConfig(HttpServer server,
                        Function<? super HttpServer, ? extends HttpServer> tcpServerMapper,
                        String name) {
        super(server,name);
        this.tcpServerMapper = Objects.requireNonNull(tcpServerMapper, "tcpServerMapper");
        this.name = "HttpServerTcpConfig " + name;
        System.out.println(String.format("save mapper %s",this.name));
    }

    @Override
    protected HttpServer tcpConfiguration() {
        System.out.println(this.name + " tcpConfiguration()");
        return Objects.requireNonNull(tcpServerMapper.apply(source.tcpConfiguration()),
                "tcpServerMapper");
    }
}

当所有的配置都添加完成,最后就需要调用我们的bind()来真正启动服务器了

public class ServerTest {
    public static void main(String[] args) {
        HttpServer.create()
                .host("0.0.0.")
                .port(8080)
                .compress("true")
                .forwarded("true")
                .bind();
    }
}

但是具体启动服务器的时候那些添加的配置执行逻辑都放在我们的HttpServerTcpConfig对象中,所以肯定是由HttpServerTcpConfig来具体实现每一个配置的tcpConfiguration(),当调用到最后的HttpServerBind对象的tcpConfiguration() ,会返回真正的HttpServer,然后再反向一步步调用apply(),为服务器添加真正的配置

//httpserver
public final Mono bind() {
    System.out.println("http server bind()");
    return bind(tcpConfiguration());
}
//HttpServerTcpConfig
protected HttpServer tcpConfiguration() {
    System.out.println(this.name + " tcpConfiguration()");
    return Objects.requireNonNull(tcpServerMapper.apply(source.tcpConfiguration()),
            "tcpServerMapper");
}
//HttpServerBind
protected HttpServer tcpConfiguration() {
	return DEFAULT_HTTP_SERVER;
}

最后执行httpserver#bind(),这是一个抽象方法,因为真正的绑定操作肯定是由HttpServerOperator来实现的,它会调用source的bind()

protected Mono bind(HttpServer b) {
    System.out.println(this.name + " bind()");
    return source.bind(b);
}

直到最后的HttpServerBind#bind(),到这里http层面的配置逻辑就完成,实际的代码中这里开始执行tcp层面的配置启动逻辑,也是一样的逻辑,这里不再赘述

protected HttpServer tcpConfiguration() {
	return DEFAULT_HTTP_SERVER;
}

最后看一下我们在标准输出里打印的执行流程

create http server instance
save source HttpServerOperator host
save mapper HttpServerTcpConfig host
save source HttpServerOperator port
save mapper HttpServerTcpConfig port
save source HttpServerOperator compress
save mapper HttpServerTcpConfig compress
save source HttpServerOperator forwarded
save mapper HttpServerTcpConfig forwarded
http server bind()
HttpServerTcpConfig forwarded tcpConfiguration()
HttpServerTcpConfig compress tcpConfiguration()
HttpServerTcpConfig port tcpConfiguration()
HttpServerTcpConfig host tcpConfiguration()
get really http server!
httpserver set host :0.0.0.
httpserver set port :8080
httpserver set compress :true
httpserver set forwarded :true
HttpServerOperator forwarded bind()
HttpServerOperator compress bind()
HttpServerOperator port bind()
HttpServerOperator host bind()
http server bind finish

总结

这样最基础的一个httpserver的启动流程中如何支持函数式的原理就讲解完了,通过这层包装,可以很好的分离http层和tcp层的启动逻辑,同时又将易于变化的配置开放出来通过函数式的方式开放给上一层,很好的做到了解耦和封装

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值