Skip to content

HttpRequestFunction:直接注册 Service 方法 ​

函数路由让一个具有明确输入、输出的 Service 方法直接成为 HTTP 入口。对于只负责读取 JSON、调用 Service、输出响应的接口,可以省略单独的 Handler 类,同时保留模型、校验、业务服务和路由配置的分层。

三个类型的职责 ​

类型职责
IHttpRequestFunction<R, T>函数接口,T 为输入类型,R 为返回类型,方法签名为 R handle(T input),允许抛出异常
HttpRequestFunctionRouter保存路径、函数和 TioTypeReference<T>,按路径查找函数
HttpRequestFunctionHandler框架内部执行器,读取正文、绑定参数、调用函数并转换响应

源码中的接口名是 IHttpRequestFunction。业务不用创建 HttpRequestFunctionHandler;框架启动时创建执行器,在函数路由命中后调用它。

Service 方法只要符合单参数、有返回值的签名,就可以通过 service::method 转换为函数接口。Service 本身不必实现 IHttpRequestFunction,方法名也不必叫 handle。

完整示例:直接注册 Service ​

下面使用 Java record 定义请求与响应模型,各段代码保存为对应的独立文件。Service 通过 Aop.get 获取,配置类通过启动参数交给框架执行。

请求和响应模型 ​

java
package example.model;

public record GreetingRequest(String name) {
}
java
package example.model;

public record GreetingResponse(String message) {
}

独立参数校验工具 ​

java
package example.validation;

import example.model.GreetingRequest;
import nexus.io.tio.utils.validator.ParameterValidator;

public final class GreetingValidator {
  private GreetingValidator() {
  }

  public static String name(GreetingRequest input) {
    ParameterValidator.require(input != null, "Request body is required");
    return ParameterValidator.text(input.name(), "name", 100);
  }
}

Service ​

java
package example.service;

import example.model.GreetingRequest;
import example.model.GreetingResponse;
import example.validation.GreetingValidator;
import nexus.io.model.body.RespBodyVo;

public class GreetingService {
  public RespBodyVo greet(GreetingRequest input) {
    String name = GreetingValidator.name(input);
    GreetingResponse data = new GreetingResponse("你好," + name);
    return RespBodyVo.ok(data);
  }
}

此例的 Service 是直接面向接口的应用服务,因此返回 RespBodyVo。框架会把该返回值转换成 JSON 响应。普通实体返回值也能序列化,但不会自动套上 RespBodyVo。如果服务需要供 HTTP、定时任务和消息消费共同调用,可以让核心业务方法返回响应实体,在路由适配函数中添加 RespBodyVo.ok(data)。

全局参数异常处理 ​

java
package example.exception;

import com.alibaba.fastjson2.JSONException;
import nexus.io.model.body.RespBodyVo;
import nexus.io.model.exception.BusinessException;
import nexus.io.tio.boot.exception.TioBootExceptionHandler;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.core.ChannelContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.model.exception.ParameterValidationException;
import nexus.io.tio.websocket.common.WebSocketRequest;

public class ApiExceptionHandler implements TioBootExceptionHandler {
  @Override
  public Object handler(HttpRequest request, Throwable error) {
    if (error instanceof ParameterValidationException) {
      TioRequestContext.getResponse().setStatus(400);
      return RespBodyVo.fail(400, error.getMessage());
    }
    if (error instanceof JSONException) {
      TioRequestContext.getResponse().setStatus(400);
      return RespBodyVo.fail(400, "Invalid JSON body");
    }
    if (error instanceof BusinessException business) {
      TioRequestContext.getResponse().setStatus(business.getStatus());
      return RespBodyVo.fail(business.getStatus(), business.getMessage());
    }
    TioRequestContext.getResponse().setStatus(500);
    return RespBodyVo.fail(500, "Service unavailable");
  }

  @Override
  public Object wsTextHandler(WebSocketRequest ws, String text, ChannelContext channel,
      HttpRequest request, Throwable error) {
    return RespBodyVo.fail(500, "Service unavailable");
  }

  @Override
  public Object wsBytesHandler(WebSocketRequest ws, byte[] bytes, ChannelContext channel,
      HttpRequest request, Throwable error) {
    return RespBodyVo.fail(500, "Service unavailable");
  }
}

注册 Service 方法 ​

java
package example.config;

import example.exception.ApiExceptionHandler;
import example.model.GreetingRequest;
import example.service.GreetingService;
import nexus.io.context.BootConfiguration;
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.type.TioTypeReference;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.http.server.router.HttpRequestFunctionRouter;

public class GreetingConfig implements BootConfiguration {
  @Override
  public void config() {
    TioBootServer server = TioBootServer.me();
    server.setExceptionHandler(new ApiExceptionHandler());
    HttpRequestFunctionRouter router = server.getRequestFunctionRouter();
    GreetingService service = Aop.get(GreetingService.class);

    router.add("/api/function/greeting", service::greet,
        new TioTypeReference<GreetingRequest>() {
        });
  }
}

new TioTypeReference<GreetingRequest>() { } 的匿名子类保留输入类型,框架据此解析正文。普通对象和 List<GreetingRequest> 等泛型容器都可以用这种方式声明。

函数路由的注册 API 是 add,按路径匹配,不限制 GET、POST 等方法。需要显式限制 HTTP 方法时,使用后面的 HttpRequestRouter.post 方案。不要在两套路由中注册相同路径,普通 Handler 路由在函数路由前匹配。

启动应用 ​

java
package example;

import example.config.GreetingConfig;
import nexus.io.tio.boot.TioApplication;

public class GreetingApplication {
  public static void main(String[] args) {
    TioApplication.run(GreetingApplication.class, new GreetingConfig(), args);
  }
}

向 /api/function/greeting 发送请求,使用 Content-Type: application/json:

json
{
  "name": "小明"
}

响应的 data 字段包含:

json
{
  "message": "你好,小明"
}

使用 HttpRequestRouter.post,同样可以省略 Handler 类 ​

对于要求明确 HTTP 方法、绑定 JSON 正文的业务接口,可以把很短的 HTTP 适配代码写在路由配置中。仍然按一个接口注册一个独立函数,Service 保持独立类。下面的配置可以替换上面的 GreetingConfig,复用同一个模型、校验工具和 Service。

java
package example.config;

import example.exception.ApiExceptionHandler;
import example.model.GreetingRequest;
import example.service.GreetingService;
import nexus.io.context.BootConfiguration;
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.body.RespBodyVo;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.http.server.router.HttpRequestRouter;

public class GreetingPostConfig implements BootConfiguration {
  @Override
  public void config() {
    TioBootServer server = TioBootServer.me();
    server.setExceptionHandler(new ApiExceptionHandler());
    HttpRequestRouter router = server.getRequestRouter();
    GreetingService service = Aop.get(GreetingService.class);

    router.post("/api/greeting", request -> {
      GreetingRequest input = request.getBodyObject(GreetingRequest.class);
      RespBodyVo body = service.greet(input);
      return TioRequestContext.getResponse().respond(body);
    });
  }
}

启动时将配置改为 new GreetingPostConfig()。这段代码走普通请求路由,不经过 HttpRequestFunctionHandler。它保留了方法约束和统一参数绑定,也省去了只有转发代码的 Handler 文件。适配逻辑变多时,再将函数提取为独立 Handler,便于阅读和测试。

输入绑定与返回值 ​

函数路由按声明的输入类型读取正文,不进行 Content-Type 分支或查询参数合并:

输入类型读取方式
String正文不能为空或仅含空格、制表符、换行等 trim 空白;校验后原样返回,不去除引号、不裁剪内容
byte[]正文必须有至少一个字节,原样返回二进制数据
Integer、Long、Byte、Short对应包装类型的数值转换,格式错误或溢出按参数异常处理
Float、Double数值转换,要求有限数值
Boolean正文为 true 或 false,忽略大小写
Character正文必须恰好是一个 Java char
实体、集合等复杂类型使用配置的 JSON provider,根据 TioTypeReference 解析 JSON 正文

函数执行前统一检查正文存在且字节长度大于零。JSON 解析结果必须非 null,因此空白 JSON 正文和 JSON 字面量 null 都会抛出 ParameterValidationException,不会调用 Service。字符串正文中的文本 null 是普通字符串,不按 JSON null 处理;二进制正文只检查字节长度,不检查文本空白。

{} 和 [] 可以通过基础正文校验。复杂对象的字段必填、长度,以及集合的最小元素数量仍由独立参数校验工具检查,自动绑定不代替这些约束。需要无正文的接口时,可使用 HttpRequestRouter.get 注册普通请求函数。需要读取表单或查询参数时,使用普通路由中的 request.getRequestMap();动态字段可使用 Kv,确定字段使用实体。上面的 getBodyObject() 适配只读取 JSON 正文,其解析异常保留 JSON provider 的类型,需要在全局异常处理器中映射为 HTTP 400。

返回 RespBodyVo 或普通实体时,框架生成 JSON;返回 HttpResponse 时沿用该响应;字符串和二进制使用对应响应处理逻辑。返回 null 时保留当前响应。接口需要 Long、时间或嵌套结构规范化时,可在构造 RespBodyVo 前调用 ResponseValueNormalizer.normalize(data),参见请求校验与响应转换。

校验、认证和异常 ​

函数执行前仍经过框架请求拦截器。Token 校验放在拦截器,Service 校验业务权限、资源归属和状态。直接注册 service::method 时,函数只接收声明的输入模型;签名是多个参数的业务方法需要适配函数。

框架将正文转换失败作为 ParameterValidationException 交给全局异常处理器。Service 抛出的参数异常、业务异常及其他运行时异常保留原类型;受检异常包装为运行时异常并保留 cause。应用可用统一异常策略输出 RespBodyVo,无需在每个 Service 方法中重复 try/catch。

Aop 获取的 Service 可能被多个请求共享。把请求参数保留在局部变量中,不要将当前请求、token 或当前用户写入 Service 的可变实例字段。

CORS 注解与方法引用 ​

实现 IHttpRequestFunction 的具体类,可以在类或 handle 方法上使用 @EnableCORS。框架支持继承来的公共 handle 方法;方法上的配置优先于类配置。

service::greet 和 lambda 是函数对象,目标 Service 类或 greet 方法上的注解不会自动复制到该对象。此类接口的跨域策略可以在统一 CORS 配置或拦截器中设置。需要按具体函数类配置注解时,再使用显式的 IHttpRequestFunction 实现。相关配置见跨域。

选择建议 ​

接口需求适合的写法
JSON 正文输入、单参数 Service、统一响应函数路由注册 service::method
明确 GET/POST、统一读取表单/查询参数HttpRequestRouter.get/post 加短适配函数
较多 HTTP 处理、文件流、多个服务编排独立 Handler,内部通过 Aop 调用 Service

省略 Handler 文件后,HTTP 适配职责仍由框架或路由函数承担。让 Service 保留业务逻辑、模型保留确定字段、校验和认证各自集中处理,就能减少重复代码并保持清晰的分层。

MIT Licensed