【Java 实战】Spring WebFlux 对 Controller 响应的统一处理

957 阅读4分钟

全局包装响应对象 可以为API的一致性和开发的便利性带来很大的好处。

在 Spring WebFlux 中,应该如何全局包装响应对象?

又如何通过自定义注解修饰响应对象?

响应对象

我们有如下的响应对象:

package example.shared.webflux.response;

public record Response<T>(
  int code,
  String message,
  T data
) {
    public Response(ResponseCode responseCode, T data) {
        this(
          responseCode.getCode(), 
          responseCode.getMessage(), 
          data
        );
    }
}
package example.shared.webflux.response;

public enum ResponseCode {
  SUCCESS(200, "操作成功"),
  NOT_FOUND(404, "未找到资源"),
  INTERNAL_ERROR(500, "服务器内部错误");
  
  private final int code;
  private final String message;

  ResponseCode(int code, String message) {
      this.code = code;
      this.message = message;
  }

  public int getCode() {
      return code;
  }

  public String getMessage() {
      return message;
  }
}

添加 Bean

通过 @Configuration 类注册的 WebFilter 实例会自动应用于所有的 Web 请求,这通常意味着通过控制器(controller)路由的请求。

package example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.WebFilter;

import com.fasterxml.jackson.databind.ObjectMapper;

import example.shared.webflux.response.ResponseFilter;

@Configuration
public class WebFilterConfig {

  @Bean
  public WebFilter responseWrapperFilter(
    ObjectMapper objectMapper
  ) {
    return new ResponseFilter(objectMapper);
  }

}

实现 ResponseFilter


package example.shared.webflux.response;

import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import com.fasterxml.jackson.databind.ObjectMapper;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.reactivestreams.Publisher;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;

public class ResponseFilter implements WebFilter {
  private final ObjectMapper objectMapper;

  public ResponseFilter(ObjectMapper objectMapper) {
    this.objectMapper = objectMapper;
  }

  @Override
  public Mono<Void> filter(
    ServerWebExchange exchange,
    WebFilterChain chain
  ) {
    ServerHttpResponse originalResponse = exchange.getResponse();
    ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
      @Override
      public Mono<Void> writeWith(
          Publisher<? extends DataBuffer> body
      ) {

        if (body instanceof Flux) {
          return super.writeWith(
              ResponseUtils.genFluxBody(
                exchange,
                objectMapper,
                (Flux<? extends DataBuffer>) body,
                ResponseCode.SUCCESS.getMessage()
              )
          );
        } else if (body instanceof Mono) {

          return super.writeWith(
            ResponseUtils.genMonoBody(
              exchange,
              objectMapper,
              (Mono<? extends DataBuffer>) body,
              ResponseCode.SUCCESS.getMessage()
            )
          );
        }

        return super.writeWith(body);

      };

    };
    // 使用装饰后的响应对象来继续处理链
    return chain.filter(
        exchange.mutate()
          .response(decoratedResponse)
          .build()
      );

  }


}

filter 方法

@Override
public Mono<Void> filter(
  ServerWebExchange exchange,
  WebFilterChain chain
) {
  ...
}
  • filter 方法是 WebFilter 接口的一部分,它处理传入的ServerWebExchange 和 WebFilterChain。
  • ServerWebExchange 是一个 HTTP 请求-响应交换的契约,封装了请求和响应的上下文信息。
  • WebFilterChain 用于将请求传递给链中的下一个过滤器。

响应装饰和修改

ServerHttpResponse originalResponse = exchange.getResponse();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
  @Override
  public Mono<Void> writeWith(
      Publisher<? extends DataBuffer> body
  ) {
    ...
  }
};
  • 此代码段首先获取原始的 ServerHttpResponse。
  • 然后创建一个 ServerHttpResponseDecorator 的匿名子类,以便自定义写入响应的方式。
  • writeWith 方法重写了父类的方法,用于处理响应体的写入。

自定义响应体处理

if (body instanceof Flux) {
  return super.writeWith(
    ResponseUtils.genFluxBody(
      exchange,
      objectMapper,
      (Flux<? extends DataBuffer>) body
    )
  );
} else if (body instanceof Mono) {
  return super.writeWith(
    ResponseUtils.genMonoBody(
      exchange,
      objectMapper,
      (Mono<? extends DataBuffer>) body
    )
  );
}
return super.writeWith(body);
  • 这段代码检查响应体是 Flux 还是 Mono 类型(Reactor 核心类型,用于处理异步数据流)。
  • 对于 Flux 和 Mono 类型的数据,分别调用 ResponseUtils.genFluxBody 和 ResponseUtils.genMonoBody 方法来生成新的响应体。这两个方法会对响应数据进行一些自定义处理(例如,格式转换、数据包装等)。
  • 最后使用 super.writeWith 方法将处理后的数据写入响应中。

应用

return chain.filter(
    exchange.mutate()
      .response(decoratedResponse)
      .build()
  );
  • 通过调用 exchange.mutate() 创建 ServerWebExchange 的一个变异(mutated)版本,并设置自定义的响应装饰器 decoratedResponse。
  • 使用 .build() 构建新的 ServerWebExchange 实例。
  • 调用 chain.filter 以继续处理请求链,但使用已修改的响应。

转换方法的具体实现

package example.shared.webflux.response;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

import org.reactivestreams.Publisher;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class ResponseUtils {

  static public StringBuilder dataBuffersToStringBuilder(
      List<? extends DataBuffer> dataBuffers
  ) {
    StringBuilder responseStrBuilder = new StringBuilder();

    dataBuffers.forEach(dataBuffer -> {
      byte[] content = new byte[dataBuffer.readableByteCount()];
      dataBuffer.read(content);
      responseStrBuilder.append(
          new String(content, StandardCharsets.UTF_8));
    });

    return responseStrBuilder;
  }

  static public Response<Object> genErrorResponse(
      Exception e) {
    return new Response<>(
        ResponseCode.INTERNAL_ERROR.getCode(),
        e.getMessage(),
        null);
  }
  
  static public byte[] genErrorResponseAsBytes(
    ObjectMapper objectMapper,
    Exception e
  ) {
    byte[] bs = {};
    try {
      bs = objectMapper.writeValueAsBytes(
        ResponseUtils.genErrorResponse(e)
      );
    } catch (JsonProcessingException e1) {
      e1.printStackTrace();
    }
    return bs;
  }


  static public Publisher<? extends DataBuffer> genFluxBody(
      ServerWebExchange exchange,
      ObjectMapper objectMapper,
      Flux<? extends DataBuffer> fluxBody,
      String successMessage
  ) {
    ServerHttpResponse originalResponse = exchange.getResponse();

    return fluxBody.buffer().map(dataBuffers -> {
      // 将响应体合并并转换为字符串
      StringBuilder responseStrBuilder = ResponseUtils.dataBuffersToStringBuilder(dataBuffers);
      String responseStr = responseStrBuilder.toString();

      // 响应体的字节数组
      byte[] bytes;

      /* <> 生成响应对象 apiResponse */
      // 反序列化原始响应数据
      Object responseData = null;
      try {
        responseData = objectMapper.readValue(responseStr, Object.class);
      } catch (IOException e) {
        responseData = responseStr;
      }
      
      Response<Object> apiResponse = new Response<>(
          ResponseCode.SUCCESS.getCode(),
          successMessage,
          responseData
      );
      /* 生成响应对象 apiResponse end </> */

      try {
        bytes = objectMapper.writeValueAsBytes(apiResponse);
      } catch (JsonProcessingException e) {
        bytes = ResponseUtils.genErrorResponseAsBytes(
          objectMapper,
          e
        );
      }
      originalResponse.getHeaders().setContentLength(bytes.length);
      originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
      return exchange.getResponse().bufferFactory().wrap(bytes);
    });

  }

  static public Publisher<? extends DataBuffer> genMonoBody(
      ServerWebExchange exchange,
      ObjectMapper objectMapper,
      Mono<? extends DataBuffer> monoBody,
      String successMessage
  ) {
    ServerHttpResponse originalResponse = exchange.getResponse();

    return monoBody.map(dataBuffer -> {
      // 将响应体合并并转换为字符串
      StringBuilder responseStrBuilder = ResponseUtils.dataBuffersToStringBuilder(List.of(dataBuffer));
      String responseStr = responseStrBuilder.toString();

      // 响应体的字节数组
      byte[] bytes = {};

      /* <> 生成响应对象 apiResponse */

      // 反序列化原始响应数据
      Object responseData = null;

      Response<?> apiResponse = null;

      /* 
        无需对 Response 再次包装 
      */
      try {
          apiResponse = objectMapper.readValue(
            responseStr, 
            Response.class
          );

        } catch (IOException e) {

          // 如果反序列化失败
          System.out.println(
            "responseStr: " + responseStr
          );

        } 

      if (
        apiResponse == null 
        || ( // apiResponse 未完成
          apiResponse.code() == 0
          && apiResponse.message() == null
        )
      ) {
        try {
          responseData = objectMapper.readValue(responseStr, Object.class);
        } catch (IOException e) {
          responseData = responseStr;
        };

        apiResponse = new Response<>(
            ResponseCode.SUCCESS.getCode(),
            successMessage,
            responseData
        );
      }

      /* 生成响应对象 apiResponse end </> */

      try {
        bytes = objectMapper.writeValueAsBytes(apiResponse);
      } catch (JsonProcessingException e) {
        bytes = ResponseUtils.genErrorResponseAsBytes(
          objectMapper,
          e
        );
      }
    

      originalResponse.getHeaders().setContentLength(bytes.length);
      originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
      return exchange.getResponse().bufferFactory().wrap(bytes);
    });
  }
}

自定义成功消息

如何使用自定义注解,来定义 Response 成功时的消息?

用法类似这样:

@GetMapping("/public/greeting")
@SuccessMessage("自定义成功消息!")
public Greeting publicGreeting (
  @RequestParam(name = "name", defaultValue = "public")
  String name 
) {
  
  return new Greeting(
    counter.incrementAndGet(),
    String.format(temp, name)
  );
}

自定义注解


package example.shared.webflux.response;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SuccessMessage {
    String value();
}

获取注解信息

public class ResponseFilter implements WebFilter {
  // ...
  private String getSuccessMessage(
    ServerWebExchange exchange
  ) {
    /*
      这一行代码从 ServerWebExchange 中获取当前请求的处理程序对象。ServerWebExchange 是 Spring WebFlux 中用来表示当前请求和响应的上下文。BEST_MATCHING_HANDLER_ATTRIBUTE 是一个键,用来获取处理当前请求的控制器方法。
    */
    Object handler = exchange.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);

    if (handler instanceof HandlerMethod) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        SuccessMessage annotation = handlerMethod.getMethodAnnotation(SuccessMessage.class);
        if (annotation != null) {
            return annotation.value();
        }
    }
    return null;
  }

}

修改 filter 方法

String successMessage = getSuccessMessage(exchange);
if (successMessage == null) {
  successMessage = ResponseCode.SUCCESS.getMessage();
}

if (body instanceof Flux) {
  return super.writeWith(
      ResponseUtils.genFluxBody(
        exchange,
        objectMapper,
        (Flux<? extends DataBuffer>) body,
        successMessage
      )
  );
} else if (body instanceof Mono) {

  return super.writeWith(
    ResponseUtils.genMonoBody(
      exchange,
      objectMapper,
      (Mono<? extends DataBuffer>) body,
      successMessage
    )
  );
}

return super.writeWith(body);

在Spring WebFlux框架中,WebFilter 是执行各种中间层逻辑(如日志记录、请求验证、响应修改等)的理想选择。

ServerHttpResponseDecorator 修改响应体,可以获取到自定义注解的信息,从而实现自定义的响应体处理。这样可以更灵活地处理成功响应的消息,增强了代码的可读性和可维护性。