- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我很难找到与此特定场景相关的任何内容
我配置了 Spring Boot,并且在其中使用响应式(Reactive) WebClient
来使用 REST Api。我已将其配置为使用 gson
,但想知道如何为更复杂的对象添加自定义 TypeAdapters
。
我发现的只是对 WebClient.Builder.codecs()
的引用,它似乎只使用使用 ObjectMapper
的 Jackson 转换器。
这根本不可能吗?
最佳答案
这似乎是对我有用的方法。它主要基于适用于 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;
}
}
}
编码器:
@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();
}
}
关于java - 使用 gson 为 WebClient 设置自定义编码器/解码器或 typeAdapter,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60854660/
我正在编写一个类库来在我无法控制的站点上执行操作。该网站正在接受表单帖子作为输入。 谁能告诉我这两种方法除了上传数据的形式之外是否有区别? System.Net.WebClient.Uploa
用于工作的代码。 有问题的网址是 https://yobit.net/api/3/info 它适用于 IE。它曾经与 webclient 一起使用。它现在在 webclient 中不起作用。我想知道问
因此,我将我的 WebClient 包装在一个 using 语句中。但是我突然想知道,如果我的对象实现了 IDisposable 并且包装在 using 语句中,我是否需要取消订阅事件? 下面是我当前
我正在 VS15 测试版中工作并尝试使用 WebClient。虽然 System.Net 被引用,并且智能感知建议 WebClient 类可用,但在构建时我收到以下错误: The type or na
我想知道是否可以将 cookie 从一个 Web 客户端复制到另一个 Web 客户端。 原因 我正在使用并行 Web 请求,它会在每个新线程上创建 Web 客户端的新实例。 问题 信息敏感,需要使用p
我正在尝试使用 WebClient,但它给我错误,所以我检查了几个论坛(包括这个),他们告诉我把它放在哪里 在文件的顶部: using System.Net 在我想使用 WebClient 的地方之后
我正在尝试使用 WebClient 实现以下场景。使用 RestTemplate 很简单,但我不能再这样做了。 伪java代码中Spring Controller 的相关部分: Mono t1 = w
我正在使用 Spring WebClient 调用休息服务。如下所述的 post 调用代码。 Mono response = client.post()
正在尝试使用 WebClient在 Blazor 项目中。 得到以下错误: 在 blazor.webassembly.js:1 WASM: System.Net.WebException: An ex
我正在使用 ASP.NET Core 并尝试将文件下载到绝对路径。 但我遇到的问题是文件总是被下载到项目目录,文件名本身得到整个路径的名称。 我的代码: string path = @"C:\User
我需要自动化涉及使用登录表单的网站的流程。我需要在登录页面之后的页面中捕获一些数据。 我知道如何对普通页面进行屏幕抓取,但不知道如何抓取安全站点背后的页面。 这可以通过 .NET WebClient
我正在尝试逐步下载一系列序列化数据。目标是从服务器发送一个大块,并在下载时在客户端对其进行部分处理。 我正在使用 System.Net.WebClient 类并将其 AllowReadStreamBu
我在 Windows 桌面应用程序上使用此代码来获取组合框的值,之后我需要选择哪个组合框将使用 JavaScript 使用新信息更新页面 private WebBrowser withEventsFi
我正在尝试通过 C# 代码获取网站的 HTML 源代码。当我使用 Windows 身份验证访问站点时,以下代码有效: using (WebClient client = new WebClient()
我只是使用WebClient.DownloadString(),但速度慢得惊人。最大速度为 40kbs 我尝试将 WebClient.Proxy 设置为 null,但这不起作用,而且我还没有达到最大互
为了利用新的 WebClient API,我在我的 Intellij 项目中包含了 spring-webflux。 dependencies { implementation 'org.spr
我已经开始使用 WebClient,并使用过滤器方法添加请求/响应日志记录: WebClient.builder() .baseUrl(properties.getEndpoint())
我正在使用 WebClient.DownloadFile 将图像下载到本地存储库,如下所示: WebClient myWC = new WebClient();
我尝试使用网络客户端非阻塞方法验证验证码响应。所以它的工作,但我需要我的方法返回 boolean 值而不是异常。我如何从订阅返回值? webClient
这个问题已经有答案了: What does a "Cannot find symbol" or "Cannot resolve symbol" error mean? (18 个回答) 已关闭 3 年
我是一名优秀的程序员,十分优秀!