序
全局包装响应对象 可以为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 修改响应体,可以获取到自定义注解的信息,从而实现自定义的响应体处理。这样可以更灵活地处理成功响应的消息,增强了代码的可读性和可维护性。