使用gson为WebClient设置自定义编码器/解码器或typeAdapter [英] set custom encoder/decoder or typeAdapter for WebClient using gson

查看:116
本文介绍了使用gson为WebClient设置自定义编码器/解码器或typeAdapter的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很难找到与此特定情况有关的任何东西

I'm having difficulty finding anything relating to this particular scenario

我已配置了spring boot,并且在其中使用响应式WebClient来消耗REST Api. 我已将其配置为使用gson,但是想知道如何为更复杂的对象添加我的自定义TypeAdapters.

I have spring boot configured, and in it i am using the reactive WebClient to consume a REST Api. I have configured this to use gson, however would like to know how to add my custom TypeAdapters for more complicated objects.

我发现的所有内容都是对WebClient.Builder.codecs()的引用,该引用似乎只使用ObjectMapper进行杰克逊转换器.

All i'm finding are references to WebClient.Builder.codecs() which seems to take only Jackson converters using ObjectMapper.

这根本不可能吗?

推荐答案

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

This seems like the approach that worked for me. It is mainly based off the Jackson code adapted to Gson. This is in no way optimised and probably has some corner cases that are missed, however it should handle basic json parsing

助手类:

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

}

编码器:

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

并初始化我的网络客户端:

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

这篇关于使用gson为WebClient设置自定义编码器/解码器或typeAdapter的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆