Skip to content

Netty-Boot ​

简介 ​

Netty-Boot 是一个基于 Netty 的轻量级 Web 中间件框架,专为高性能 Web 应用开发而设计。它充分利用了 Netty 的高效 IO 模型,支持快速开发高并发的 Web 服务。该框架整合了多种常见的中间件,包括 PostgreSQL、Redis、MongoDB、Elasticsearch、消息队列(MQ)、Dubbo 等,提供了强大的扩展能力,使开发者能够专注于业务逻辑开发。

Netty-Boot 的目标是简化 Netty 的使用,提供类似 Spring Boot 的易用性,帮助开发者轻松构建基于 Netty 的 Web 应用,同时享受 Netty 带来的高性能和可扩展性。

特性 ​

  • 高性能:基于 Netty 的异步非阻塞 IO,支持高并发和大规模连接。
  • 模块化设计:支持 PostgreSQL、Redis、MongoDB、Elasticsearch、消息队列(MQ)和 Dubbo 等中间件的无缝集成。
  • 灵活性:通过注解驱动的组件扫描和依赖注入,简化配置,增强代码的可维护性。
  • WebSocket 支持:轻松实现 WebSocket 通信,支持长连接和实时数据推送。
  • 易扩展性:通过 AOP 和自定义中间件,开发者可以扩展 Netty-Boot 的功能,满足复杂业务需求。

开源地址 ​

入门示例 ​

添加依赖 ​

在 pom.xml 中添加以下依赖配置:

xml
<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <java.version>1.8</java.version>
  <maven.compiler.source>${java.version}</maven.compiler.source>
  <maven.compiler.target>${java.version}</maven.compiler.target>
  <graalvm.version>23.1.1</graalvm.version>
  <lombok-version>1.18.44</lombok-version>
  <jfinal-aop.version>1.4.0</jfinal-aop.version>
  <fastjson2.version>2.0.52</fastjson2.version>
  <main.class>nexus.io.study.netty.boot.MainApp</main.class>
</properties>

<dependencies>
  <dependency>
    <groupId>nexus.io</groupId>
    <artifactId>netty-boot</artifactId>
    <version>1.0.9</version>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.3.3</version>
  </dependency>

  <!-- JFinal AOP -->
  <dependency>
    <groupId>nexus.io</groupId>
    <artifactId>jfinal-aop</artifactId>
    <version>${jfinal-aop.version}</version>
  </dependency>

  <!-- FastJSON2 用于 JSON 解析 -->
  <dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>${fastjson2.version}</version>
  </dependency>

  <!-- OkHttp 客户端 -->
  <dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>3.11.0</version>
  </dependency>

  <!-- Lombok 用于简化代码 -->
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok-version}</version>
    <optional>true</optional>
    <scope>provided</scope>
  </dependency>

  <!-- JUnit 用于测试 -->
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>
</dependencies>

<profiles>
  <!-- development -->
  <profile>
    <id>development</id>
    <activation>
      <activeByDefault>true</activeByDefault>
    </activation>
    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <version>2.7.18</version>
          <configuration>
            <fork>true</fork>
            <mainClass>${main.class}</mainClass>
            <excludeGroupIds>org.projectlombok</excludeGroupIds>
            <arguments>
              <argument>--mode=dev</argument>
            </arguments>
          </configuration>
        </plugin>
      </plugins>
    </build>
  </profile>

  <!-- production -->
  <profile>
    <id>production</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <version>2.7.18</version>
          <configuration>
            <mainClass>${main.class}</mainClass>
            <excludeGroupIds>org.projectlombok</excludeGroupIds>
          </configuration>
          <executions>
            <execution>
              <goals>
                <goal>repackage</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

配置文件 ​

app.properties

properties
server.port=80
server.context-path=/im

启动类 ​

java
package nexus.io.study.netty.boot;

import nexus.io.annotation.AComponentScan;
import nexus.io.netty.boot.NettyApplication;

@AComponentScan
public class NettyHelloApp {
  public static void main(String[] args) {
    NettyApplication.run(NettyHelloApp.class, args);
  }
}

配置类 ​

java
package nexus.io.study.netty.boot.config;

import java.util.function.Supplier;

import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.jfinal.aop.Aop;
import nexus.io.netty.boot.http.HttpRequestRouter;
import nexus.io.netty.boot.server.NettyBootServer;
import nexus.io.netty.boot.websocket.WebsocketRouter;
import nexus.io.study.netty.boot.handler.OkHandler;
import nexus.io.study.netty.boot.handler.Ws2Handler;
import nexus.io.study.netty.boot.handler.WsHandler;

import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;

@AConfiguration
public class WebConfig {

  @Initialization
  public void config() {
    // 设置 HTTP 路由
    HttpRequestRouter httpRouter = NettyBootServer.me().getHttpRequestRouter();
    OkHandler okHandler = Aop.get(OkHandler.class);
    httpRouter.add("/txt", okHandler::txt);
    httpRouter.add("/json", okHandler::json);
    httpRouter.add("/echo", okHandler::echo);

    // 设置 WebSocket 路由
    WebsocketRouter websocketRouter = NettyBootServer.me().getWebsocketRouter();
    Supplier<SimpleChannelInboundHandler<WebSocketFrame>> wsHandlerSupplier = WsHandler::new;
    websocketRouter.add("/ws", wsHandlerSupplier);
    websocketRouter.add("/ws2", Ws2Handler::new);
  }
}

HTTP 请求处理器 ​

java
package nexus.io.study.netty.boot.handler;

import nexus.io.model.body.RespBodyVo;
import nexus.io.netty.boot.utils.HttpRequestUtils;
import nexus.io.netty.boot.utils.HttpResponseUtils;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;

public class OkHandler {

  public FullHttpResponse txt(ChannelHandlerContext ctx, FullHttpRequest httpRequest) throws Exception {
    String responseContent = "Hello, this is the HTTP response!";
    return HttpResponseUtils.txt(responseContent);
  }

  public FullHttpResponse json(ChannelHandlerContext ctx, FullHttpRequest httpRequest) throws Exception {
    RespBodyVo ok = RespBodyVo.ok();
    return HttpResponseUtils.json(ok);
  }

  public FullHttpResponse echo(ChannelHandlerContext ctx, FullHttpRequest httpRequest) {
    String fullHttpRequestString = HttpRequestUtils.getFullHttpRequestAsString(httpRequest);
    return HttpResponseUtils.txt(fullHttpRequestString);
  }
}

WebSocket 请求处理器 ​

WsHandler ​

java
package nexus.io.study.netty.boot.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class WsHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

  @Override
  protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
    if (frame instanceof TextWebSocketFrame) {
      String requestText = ((TextWebSocketFrame) frame).text();
      // 返回相同的消息
      ctx.channel().writeAndFlush(new TextWebSocketFrame("Server received: " + requestText));

    } else if (frame instanceof BinaryWebSocketFrame) {
      // 处理二进制帧
      // 可根据需求实现
    } else if (frame instanceof CloseWebSocketFrame) {
      ctx.channel().close();

    } else if (frame instanceof PingWebSocketFrame) {
      ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content().retain()));

    } else {
      throw new UnsupportedOperationException("不支持的帧类型: " + frame.getClass().getName());
    }
  }

  @Override
  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    log.info("连接加入: {}", ctx.channel());
  }

  @Override
  public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    log.info("连接移除: {}", ctx.channel());
  }
}

Ws2Handler ​

java
package nexus.io.study.netty.boot.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Ws2Handler extends SimpleChannelInboundHandler<WebSocketFrame> {

  @Override
  protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
    if (frame instanceof TextWebSocketFrame) {
      String requestText = ((TextWebSocketFrame) frame).text();
      // 返回相同的消息
      ctx.channel().writeAndFlush(new TextWebSocketFrame("Server received: " + requestText));

    } else if (frame instanceof BinaryWebSocketFrame) {
      // 处理二进制帧
      // 可根据需求实现
    } else if (frame instanceof CloseWebSocketFrame) {
      ctx.channel().close();

    } else if (frame instanceof PingWebSocketFrame) {
      ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content().retain()));

    } else {
      throw new UnsupportedOperationException("不支持的帧类型: " + frame.getClass().getName());
    }
  }

  @Override
  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    log.info("连接加入: {}", ctx.channel());
  }

  @Override
  public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    log.info("连接移除: {}", ctx.channel());
  }
}

启动指南 ​

1. 构建项目 ​

确保已安装 Maven 和 JDK 1.8 或更高版本。

在项目根目录下运行以下命令以构建项目:

bash
mvn clean install

2. 运行应用 ​

使用以下命令启动应用程序:

bash
mvn spring-boot:run -Pdevelopment

此命令将使用 development 配置文件启动应用,并在 app.properties 中配置的端口(默认 80)上运行。

3. 配置端口和上下文路径 ​

如果需要更改服务器端口或上下文路径,可以修改 app.properties 文件:

properties
server.port=8080
server.context-path=/api

重新构建并运行应用以应用更改。

测试指南 ​

1. 测试 HTTP 接口 ​

测试 /txt 接口 ​

使用 curl 命令发送 GET 请求:

bash
curl http://localhost:80/im/txt

预期响应:

Hello, this is the HTTP response!

测试 /json 接口 ​

使用 curl 命令发送 GET 请求:

bash
curl http://localhost:80/im/json

预期响应(JSON 格式):

json
{
  "status": "OK",
  "message": "Request successful"
}

测试 /echo 接口 ​

使用 curl 命令发送 POST 请求:

bash
curl -X POST http://localhost:80/im/echo -d "This is an echo test"

预期响应:

This is an echo test

2. 测试 WebSocket 接口 ​

您可以使用 WebSocket 客户端工具 或 WebSocket UI 来测试 WebSocket 接口。

连接到 /ws 路由 ​

  1. 打开 WebSocket 客户端工具。
  2. 输入连接地址:
ws://localhost:80/im/ws
  1. 连接后,发送一条消息,例如:
Hello WebSocket!
  1. 预期响应:
Server received: Hello WebSocket!

连接到 /ws2 路由 ​

  1. 打开另一个 WebSocket 客户端窗口。
  2. 输入连接地址:
ws://localhost:80/im/ws2
  1. 连接后,发送一条消息,例如:
Hello WebSocket 2!
  1. 预期响应:
Server received: Hello WebSocket 2!

3. 日志查看 ​

应用启动后,日志将输出到控制台。您可以查看连接的添加和移除日志,以及其他调试信息。例如:

INFO  nexus.io.study.netty.boot.handler.WsHandler - 连接加入: [id: 0x12345678, L:/127.0.0.1:80 - R:/127.0.0.1:54321]
INFO  nexus.io.study.netty.boot.handler.WsHandler - 连接移除: [id: 0x12345678, L:/127.0.0.1:80 - R:/127.0.0.1:54321]

常见问题 ​

1. 端口被占用 ​

如果启动时提示端口被占用,可以在 app.properties 中更改 server.port,或终止占用该端口的进程。

2. 依赖冲突 ​

确保 pom.xml 中的依赖版本与项目需求匹配,避免版本冲突。可以使用 mvn dependency:tree 查看依赖树。

3. WebSocket 连接失败 ​

确保服务器已正确启动,并且客户端连接地址与服务器配置匹配(端口和上下文路径)。

MIT Licensed