简体   繁体   English

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

[英]set custom encoder/decoder or typeAdapter for WebClient using gson

I'm having difficulty finding anything relating to this particular scenario我很难找到与此特定场景相关的任何内容

I have spring boot configured, and in it i am using the reactive WebClient to consume a REST Api.我已经配置了 spring boot,在其中我使用反应式WebClient来使用 REST Api。 I have configured this to use gson , however would like to know how to add my custom TypeAdapters for more complicated objects.我已将其配置为使用gson ,但是想知道如何为更复杂的对象添加我的自定义TypeAdapters

All i'm finding are references to WebClient.Builder.codecs() which seems to take only Jackson converters using ObjectMapper .我发现的所有内容都是对WebClient.Builder.codecs()引用,它似乎只使用使用ObjectMapper Jackson 转换器。

Is this not possible at all?这根本不可能吗?

This seems like the approach that worked for me.这似乎是对我有用的方法。 It is mainly based off the Jackson code adapted to Gson.它主要基于适用于 Gson 的 Jackson 代码。 This is in no way optimised and probably has some corner cases that are missed, however it should handle basic json parsing这绝不是优化的,可能有一些漏掉的极端情况,但是它应该处理基本的 json 解析

Helper Class:辅助类:

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;
    }

}

Decoder:解码器:

@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;
    }
}

Config for app:应用程序配置:

@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();
    }
}

And initialisation of my webclient:并初始化我的网络客户端:

@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();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM