使用 gson 为 WebClient 设置自定义编码器/解码器或 typeAdapter

2023-12-13

我很难找到与这个特定场景相关的任何内容

我配置了 spring boot,并且在其中使用了反应式WebClient使用 REST Api。 我已将其配置为使用gson,但是想知道如何添加我的自定义TypeAdapters对于更复杂的物体。

我所找到的只是参考WebClient.Builder.codecs()这似乎只需要杰克逊转换器使用ObjectMapper.

难道这根本不可能吗?


这似乎是对我有用的方法。它主要基于适用于 Gson 的 Jackson 代码。这绝不是优化的,可能有一些遗漏的极端情况,但它应该处理基本的 json 解析

助手类:

class GsonEncoding {
    static final List<MimeType> mimeTypes = Stream.of(new MimeType("application", "json"),
                                                              new MimeType("application", "*+json"))
                                                          .collect(Collectors.toUnmodifiableList());

    static final byte[]                 NEWLINE_SEPARATOR = {'\n'};
    static final Map<MediaType, byte[]> STREAM_SEPARATORS;

    static {
        STREAM_SEPARATORS = new HashMap<>();
        STREAM_SEPARATORS.put(MediaType.APPLICATION_STREAM_JSON, NEWLINE_SEPARATOR);
        STREAM_SEPARATORS.put(MediaType.parseMediaType("application/stream+x-jackson-smile"), new byte[0]);
    }

    static void logValue(final Logger log, @Nullable Map<String, Object> hints, Object value) {
        if (!Hints.isLoggingSuppressed(hints)) {
            if (log.isLoggable(Level.FINE)) {
                boolean traceEnabled = log.isLoggable(Level.FINEST);
                String message = Hints.getLogPrefix(hints) + "Encoding [" + LogFormatUtils.formatValue(value, !traceEnabled) + "]";
                if (traceEnabled) {
                    log.log(Level.FINEST, message);
                } else {
                    log.log(Level.FINE, message);
                }
            }
        }
    }

    static boolean supportsMimeType(@Nullable MimeType mimeType) {
        return (mimeType == null || GsonEncoding.mimeTypes.stream().anyMatch(m -> m.isCompatibleWith(mimeType)));
    }

    static boolean isTypeAdapterAvailable(Gson gson, Class<?> clazz) {
        try {
            gson.getAdapter(clazz);
            return true;
        } catch(final IllegalArgumentException e) {
            return false;
        }
    }

}

Encoder:

@Log
@RequiredArgsConstructor
@Component
public class GsonEncoder implements HttpMessageEncoder<Object> {

    private final Gson gson;

    @Override
    public List<MediaType> getStreamingMediaTypes() {
        return Collections.singletonList(MediaType.APPLICATION_STREAM_JSON);
    }

    @Override
    public boolean canEncode(final ResolvableType elementType, final MimeType mimeType) {
        Class<?> clazz = elementType.toClass();
        if (!GsonEncoding.supportsMimeType(mimeType)) {
            return false;
        }
        if (Object.class == clazz) {
            return true;
        }
        if (!String.class.isAssignableFrom(elementType.resolve(clazz))) {
           return GsonEncoding.isTypeAdapterAvailable(gson, clazz);
        }
        return false;
    }

    @Override
    public Flux<DataBuffer> encode(final Publisher<?> inputStream, final DataBufferFactory bufferFactory, final ResolvableType elementType, final MimeType mimeType, final Map<String, Object> hints) {
        Assert.notNull(inputStream, "'inputStream' must not be null");
        Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
        Assert.notNull(elementType, "'elementType' must not be null");

        if (inputStream instanceof Mono) {
            return Mono.from(inputStream)
                       .map(value -> encodeValue(value, bufferFactory, elementType, mimeType, hints))
                       .flux();
        } else {
            byte[] separator = streamSeparator(mimeType);
            if (separator != null) { // streaming
                try {
                    return Flux.from(inputStream)
                               .map(value -> encodeStreamingValue(value, bufferFactory, hints, separator));
                } catch (Exception ex) {
                    return Flux.error(ex);
                }
            } else { // non-streaming
                ResolvableType listType = ResolvableType.forClassWithGenerics(List.class, elementType);
                return Flux.from(inputStream)
                           .collectList()
                           .map(list -> encodeValue(list, bufferFactory, listType, mimeType, hints))
                           .flux();
            }

        }
    }

    @Nullable
    private byte[] streamSeparator(@Nullable MimeType mimeType) {
        for (MediaType streamingMediaType : this.getStreamingMediaTypes()) {
            if (streamingMediaType.isCompatibleWith(mimeType)) {
                return GsonEncoding.STREAM_SEPARATORS.getOrDefault(streamingMediaType, GsonEncoding.NEWLINE_SEPARATOR);
            }
        }
        return null;
    }


    @Override
    public List<MimeType> getEncodableMimeTypes() {
        return GsonEncoding.mimeTypes;
    }


    @Override
    public DataBuffer encodeValue(Object value, DataBufferFactory bufferFactory, ResolvableType valueType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
        GsonEncoding.logValue(log, hints, value);
        byte[] bytes = gson.toJson(value).getBytes();
        DataBuffer buffer = bufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }


    private DataBuffer encodeStreamingValue(Object value, DataBufferFactory bufferFactory, @Nullable Map<String, Object> hints, byte[] separator) {
        GsonEncoding.logValue(log, hints, value);
        byte[] bytes = gson.toJson(value).getBytes();
        int offset;
        int length;
        offset = 0;
        length = bytes.length;
        DataBuffer buffer = bufferFactory.allocateBuffer(length + separator.length);
        buffer.write(bytes, offset, length);
        buffer.write(separator);
        return buffer;
    }

}

解码器:

@Log
@RequiredArgsConstructor
@Component
public class GsonDecoder implements HttpMessageDecoder<Object> {

    private static final int MAX_IN_MEMORY_SIZE = 2000 * 1000000;

    private final Gson gson;

    @Override
    public Map<String, Object> getDecodeHints(final ResolvableType resolvableType, final ResolvableType elementType, final ServerHttpRequest request, final ServerHttpResponse response) {
        return Hints.none();
    }

    @Override
    public boolean canDecode(final ResolvableType elementType, final MimeType mimeType) {
        if (CharSequence.class.isAssignableFrom(elementType.toClass())) {
            return false;
        }
        if (!GsonEncoding.supportsMimeType(mimeType)) {
            return false;
        }
        return GsonEncoding.isTypeAdapterAvailable(gson, elementType.getRawClass());
    }

    @Override
    public Object decode(DataBuffer dataBuffer, ResolvableType targetType,
                         @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) throws DecodingException {

        return decodeInternal(dataBuffer, targetType, hints);
    }

    private Object decodeInternal(final DataBuffer dataBuffer, final ResolvableType targetType, @Nullable Map<String, Object> hints) {
        try {
            final Object value = gson.fromJson(new InputStreamReader(dataBuffer.asInputStream()), targetType.getRawClass());
            GsonEncoding.logValue(log, hints, value);
            return value;
        } finally {
            DataBufferUtils.release(dataBuffer);
        }
    }


    @Override
    public Flux<Object> decode(Publisher<DataBuffer> input, ResolvableType elementType,
                               @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
        return Flux.from(input).map(d -> decodeInternal(d, elementType, hints));
    }

    @Override
    public Mono<Object> decodeToMono(final Publisher<DataBuffer> inputStream, final ResolvableType elementType, final MimeType mimeType, final Map<String, Object> hints) {
        return DataBufferUtils.join(inputStream, MAX_IN_MEMORY_SIZE)
                              .flatMap(dataBuffer -> Mono.justOrEmpty(decode(dataBuffer, elementType, mimeType, hints)));
    }

    @Override
    public List<MimeType> getDecodableMimeTypes() {
        return GsonEncoding.mimeTypes;
    }
}

应用程序配置:

@Configuration
public class ApplicationConfiguration {

    @Bean
    public Gson gson(){
        final GsonBuilder gsonBuilder = new GsonBuilder();
// for each of your TypeAdapters here call gsonBuilder.registerTypeAdapter()
        return gsonBuilder.create();
    }
}

以及我的网络客户端的初始化:

@Service
@RequiredArgsConstructor
@Log
public class MyApiClient {


    private final GsonEncoder encoder;
    private final GsonDecoder decoder;

    private static final int CONNECTION_TIMEOUT = 5000;

    @PostConstruct
    public void init() {
        client = WebClient.builder()
                          .baseUrl("http://myresource.com")
                          .clientConnector(new ReactorClientHttpConnector(HttpClient.from(TcpClient.create()
                                                                                                   .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECTION_TIMEOUT)
                                                                                                   .doOnConnected(connection -> {
                                                                                                       connection.addHandlerLast(new ReadTimeoutHandler(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS));
                                                                                                       connection.addHandlerLast(new WriteTimeoutHandler(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS));
                                                                                                   })
                          )))
                          .defaultHeaders(h -> h.setBasicAuth(username, password))
                          .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/json")
                          .defaultHeader(HttpHeaders.ACCEPT, "application/json")
                          .defaultHeader(HttpHeaders.ACCEPT_CHARSET, "UTF-8")
                          .codecs(clientCodecConfigurer -> {
                              clientCodecConfigurer.customCodecs().register(encoder);
                              clientCodecConfigurer.customCodecs().register(decoder);
                          })
                          .build();
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 gson 为 WebClient 设置自定义编码器/解码器或 typeAdapter 的相关文章

随机推荐