主要原理是在序列化之前把接口返回值放入自定义结构体,然后按照继续按照框架的逻辑继续执行.
这个方法相比于网上搜到的方法,少了一次反序列化和一次序列化
/**
* @author zfw
*/
@Configuration
public class CodecConfig {
@Bean
@Primary
@Order(Integer.MIN_VALUE)
CodecConfigurer codecConfigurer() {
// 配置json序列化策略
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
objectMapper.registerModule(timeModule)
.registerModule(new ParameterNamesModule()).registerModules(ObjectMapper.findModules());
// 配置统一响应
BaseResultHandler baseResultHandler = new BaseResultHandler(objectMapper);
ServerCodecConfigurer serverCodecConfigurer = ServerCodecConfigurer.create();
ServerCodecConfigurer.ServerDefaultCodecs serverDefaultCodecs = serverCodecConfigurer.defaultCodecs();
serverDefaultCodecs.jackson2JsonEncoder(baseResultHandler);
return serverCodecConfigurer;
}
@Bean
@Primary
ResponseBodyResultHandler resultHandler(@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
ServerCodecConfigurer serverCodecConfigurer,
@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
return new ResultHandler(serverCodecConfigurer.getWriters(), contentTypeResolver, reactiveAdapterRegistry);
}
static class BaseResultHandler extends Jackson2JsonEncoder {
public BaseResultHandler(ObjectMapper mapper, MimeType... mimeTypes) {
super(mapper, mimeTypes);
}
@Override
public Flux<DataBuffer> encode(Publisher<?> inputStream, DataBufferFactory bufferFactory, ResolvableType elementType, MimeType mimeType, Map<String, Object> hints) {
if (inputStream instanceof Mono) {
// 处理单一对象类型数据
inputStream = Mono.from(inputStream)
.map(body -> body instanceof Response ? body : Response.ok(body))
.defaultIfEmpty(Response.ok(null));
} else {
// 处理集合类型数据
if (getStreamingMediaTypeSeparator(mimeType) == null) {
inputStream = Flux.from(inputStream)
.collectList()
// 处理分页
.zipWith(PageUtil.get(), (body, count) -> body instanceof Response ? body : MultiResponse.ok(count, body))
.defaultIfEmpty(MultiResponse.ok(0, Collections.emptyList()));
}
}
return super.encode(inputStream, bufferFactory, elementType, mimeType, hints);
}
}
}
到此,除了流式返回的数据外,自己创建的类型都能统一返回了.
如果需要把框架内部定义的数据类型(比如:String,void)也放在自己的响应结构中的话,需要继续往上追溯
public abstract class AbstractMessageWriterResultHandler extends HandlerResultHandlerSupport {
protected Mono<Void> writeBody(@Nullable Object body, MethodParameter bodyParameter,
@Nullable MethodParameter actualParam, ServerWebExchange exchange) {
if (adapter != null) {
publisher = adapter.toPublisher(body);
boolean isUnwrapped = KotlinDetector.isSuspendingFunction(bodyParameter.getMethod()) &&
!COROUTINES_FLOW_CLASS_NAME.equals(bodyType.toClass().getName());
ResolvableType genericType = isUnwrapped ? bodyType : bodyType.getGeneric();
elementType = getElementType(adapter, genericType);
actualElementType = elementType;
}
...
if (elementType.resolve() == void.class || elementType.resolve() == Void.class) {
return Mono.from((Publisher<Void>) publisher);
}
...
if (bestMediaType != null) {
String logPrefix = exchange.getLogPrefix();
if (logger.isDebugEnabled()) {
logger.debug(logPrefix +
(publisher instanceof Mono ? "0..1" : "0..N") + " [" + elementType + "]");
}
for (HttpMessageWriter<?> writer : getMessageWriters()) {
if (writer.canWrite(actualElementType, bestMediaType)) {
return writer.write((Publisher) publisher, actualType, elementType,
bestMediaType, exchange.getRequest(), exchange.getResponse(),
Hints.from(Hints.LOG_PREFIX_HINT, logPrefix));
}
}
}
...
}
}
其中有两个关键的方法 getElementType(), getMessageWriters()
getElementType(): 接口返回空值会返回Void,在下一个if中会直接返回空的Mono,后续的操作都会被跳过
getMessageWriters(): 包含了一个字符处理器
代表遇上字符的数据类型都会应用这种特殊处理方式,而text/plain;charset=UTF-8不属于json序列化的范畴,所以上边写的自定义响应处理类失效了
所以小机灵干脆直接覆盖ResponseBodyResultHandler,把待处理类型偷偷替换了,直接走对象的处理方式
/**
* @author zfw
*/
public class ResultHandler extends ResponseBodyResultHandler {
/**
* 忽略特殊处理的数据类型
*/
private static final List<Class<?>> IGNORE = new ArrayList<>();
static {
IGNORE.add(String.class);
}
public ResultHandler(List<HttpMessageWriter<?>> writers, RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) {
super(writers, resolver, registry);
}
@Override
@SuppressWarnings({"unchecked", "rawtypes", "ConstantConditions"})
protected Mono<Void> writeBody(@Nullable Object body, MethodParameter bodyParameter,
@Nullable MethodParameter actualParam, ServerWebExchange exchange) {
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
ResolvableType actualType = (actualParam != null ? ResolvableType.forMethodParameter(actualParam) : bodyType);
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(bodyType.resolve(), body);
Publisher<?> publisher;
ResolvableType elementType;
ResolvableType actualElementType;
if (adapter != null) {
publisher = adapter.toPublisher(body);
boolean isUnwrapped = KotlinDetector.isSuspendingFunction(bodyParameter.getMethod()) &&
!COROUTINES_FLOW_CLASS_NAME.equals(bodyType.toClass().getName());
ResolvableType genericType = isUnwrapped ? bodyType : bodyType.getGeneric();
elementType = getElementType(genericType);
actualElementType = elementType;
} else {
publisher = Mono.justOrEmpty(body);
actualElementType = body != null ? ResolvableType.forInstance(body) : bodyType;
elementType = (bodyType.toClass() == Object.class && body != null ? actualElementType : bodyType);
}
MediaType bestMediaType;
try {
bestMediaType = selectMediaType(exchange, () -> getMediaTypesFor(elementType));
} catch (NotAcceptableStatusException ex) {
HttpStatus statusCode = exchange.getResponse().getStatusCode();
if (statusCode != null && statusCode.isError()) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring error response content (if any). " + ex.getReason());
}
return Mono.empty();
}
throw ex;
}
if (bestMediaType != null) {
String logPrefix = exchange.getLogPrefix();
if (logger.isDebugEnabled()) {
logger.debug(logPrefix +
(publisher instanceof Mono ? "0..1" : "0..N") + " [" + elementType + "]");
}
for (HttpMessageWriter<?> writer : getMessageWriters()) {
if (writer.canWrite(actualElementType, bestMediaType)) {
return writer.write((Publisher) publisher, actualType, elementType,
bestMediaType, exchange.getRequest(), exchange.getResponse(),
Hints.from(Hints.LOG_PREFIX_HINT, logPrefix));
}
}
}
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
boolean isPresentMediaType = (contentType != null && contentType.equals(bestMediaType));
Set<MediaType> producibleTypes = exchange.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isPresentMediaType || !CollectionUtils.isEmpty(producibleTypes)) {
return Mono.error(new HttpMessageNotWritableException(
"No Encoder for [" + elementType + "] with preset Content-Type '" + contentType + "'"));
}
List<MediaType> mediaTypes = getMediaTypesFor(elementType);
if (bestMediaType == null && mediaTypes.isEmpty()) {
return Mono.error(new IllegalStateException("No HttpMessageWriter for " + elementType));
}
return Mono.error(new NotAcceptableStatusException(mediaTypes));
}
private ResolvableType getElementType(ResolvableType genericType) {
// 去除了空值和String
if (genericType == ResolvableType.NONE || IGNORE.contains(genericType.resolve())) {
return ResolvableType.forClass(Object.class);
}
return genericType;
}
private List<MediaType> getMediaTypesFor(ResolvableType elementType) {
List<MediaType> writableMediaTypes = new ArrayList<>();
for (HttpMessageWriter<?> converter : getMessageWriters()) {
if (converter.canWrite(elementType, null)) {
writableMediaTypes.addAll(converter.getWritableMediaTypes(elementType));
}
}
return writableMediaTypes;
}
}