サンプルコード
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.4'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
implementation("org.glassfish.jaxb:jaxb-runtime")
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
以下はXMLレスポンスを格納するクラス。注意点として、@XmlRootElement
の指定が必要。
import jakarta.xml.bind.annotation.XmlRootElement;
import lombok.Data;
@Data
@XmlRootElement(name = "note")
public class WebClientSampleDto {
String to;
String from;
}
public class SampleMain {
public static void main(String[] args) {
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(conf -> conf.defaultCodecs().jaxb2Decoder(new Jaxb2XmlDecoder()))
.build();
WebClient client = WebClient.builder()
.exchangeStrategies(strategies)
.build();
WebClientSampleDto body = client.get()
.uri("http://localhost:3002/xml")
.retrieve()
.bodyToMono(WebClientSampleDto.class)
.block();
System.out.println(body);
}
}
ほぼ見た目通り。デフォルトコーデックにXML処理用のJaxb2XmlDecoder
を指定する。今回はレスポンスだけなのでdecoderの指定だけだが、リクエストはjaxb2Encoder
を指定すれば良いと思われる。
詳細
動作確認
適当なモックサーバ、今回は https://mockoon.com/ を使用し、URLhttp://localhost:3002/xml
がContent-Type
はapplication/xml
で以下を返すように設定する。
<note>
<to>to-hoge</to>
<from>from-kagami</from>
</note>
これを実行すると以下の結果が得られる。
WebClientSampleDto(to=to-hoge, from=from-kagami)
対応Content-Typeの変更
Jaxb2XmlDecoder
のデフォルト対応Content-Type
は、application/xml
, text/xml
, application/*+xml
、となっている。なのでこれ以外に対応したい場合は別途以下のように設定の必要がある。具体的にはJaxb2XmlDecoder
のコンストラクタに指定する。以下はapplication/octet-stream
に対応させている。
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(conf -> conf.defaultCodecs().jaxb2Decoder(
new Jaxb2XmlDecoder(MimeTypeUtils.APPLICATION_OCTET_STREAM)))
.build();
はまった点
UnsupportedMediaTypeException
この例外はクラス名が示す通り、レスポンスに対応するデコーダーがWebClientに未設定なことを示す。ただ、その原因は様々なケースがありうる。
Caused by: org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/octet-stream;charset=utf-8' not supported for bodyType=org.example.app.WebClientSampleDto
at org.springframework.web.reactive.function.BodyExtractors.readWithMessageReaders(BodyExtractors.java:205)
まず、jaxb2Decoder
が未指定だと当然ながらこの例外をスローする。
また、Content-Typeが未対応のものだとこれも例外をスローする。
デコーダはJAXBであってjacksonではない。よって、XML用アノテーションは@XmlRootElement
を使用する必要がある。@JacksonXmlRootElement
ではない。これも例外をスローする。
参考文献
- https://stackoverflow.com/questions/73187642/how-to-retrieve-xml-using-webclient-in-spring
- https://stackoverflow.com/questions/64587787/parse-text-html-response-to-xml-with-web-client-in-spring-boot
- https://github.com/FasterXML/jackson-dataformat-xml/issues/260
- https://github.com/spring-projects/spring-framework/issues/20256