webflux 响应统一返回

本文介绍了如何通过自定义结构体减少序列化反序列化的步骤,以提高性能,并详细展示了如何处理不同类型数据的编码策略,包括流式返回和框架内置类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

主要原理是在序列化之前把接口返回值放入自定义结构体,然后按照继续按照框架的逻辑继续执行.
这个方法相比于网上搜到的方法,少了一次反序列化和一次序列化

/**
 * @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;
    }
}
<think>用户的问题是关于如何在WebFlux中重新写入响应头。我需要先理解WebFlux的工作原理,特别是响应头的处理方式。WebFlux是基于响应式编程的,通常使用Mono或Flux来处理异步数据流。响应头一旦被写入,可能无法直接修改,但用户可能需要在处理过程中调整头信息。首先,我应该考虑用户可能的场景。比如,用户可能在处理请求时,需要根据某些条件动态设置响应头,比如缓存控制、自定义头等。这时候,用户需要确保在数据写入响应体之前设置或修改头信息。接下来,我需要回忆WebFlux中处理响应头的常用方法。通常,在WebFlux中,可以通过ServerHttpResponse来直接操作响应头,但需要注意响应头只能在响应尚未提交时修改。一旦响应体开始写入,头信息可能无法更改。因此,用户需要在处理响应体之前设置头信息。可能的解决方案包括使用WebFilter来全局处理响应头,或者在具体的控制器方法中通过ServerHttpResponse来设置头。例如,在控制器方法中,可以注入ServerHttpResponse对象,然后使用其getHeaders()方法来添加或修改头信息。另外,用户可能需要处理异步流程中的头信息设置。例如,当使用Mono或Flux返回数据时,可能需要使用doOnSubscribe或doOnNext等操作符在订阅时设置头信息,确保在数据发送之前头信息已经正确设置。还需要注意,如果响应已经被提交(比如,响应体已经开始发送),那么修改头信息可能会导致异常。因此,用户需要确保在数据写入之前完成所有头信息的设置。最后,总结可能的步骤:1.在控制器方法中通过ServerHttpResponse设置头;2.使用WebFilter在请求处理流程中统一设置头;3.在响应式流中使用操作符在适当的时候设置头信息。同时提醒用户注意响应提交的时机,避免修改已提交的响应头。</think>在 WebFlux 中重新写入响应头,可通过以下方式实现:在控制器方法中注入 `ServerHttpResponse` 对象,在数据写入响应体前调用其 `getHeaders()` 方法操作头信息;或使用 `WebFilter` 全局拦截请求,在响应提交前修改头信息。需注意响应头一旦提交(如响应体开始写入)则无法修改。 ```java @GetMapping("/example") public Mono<Void> handleRequest(ServerHttpResponse response) { response.getHeaders().add("Custom-Header", "value"); return Mono.empty(); } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值