杰克逊编码器的假装客户体型序列化失败
我正在努力实施春季服务和客户端,并希望为客户端使用OpenFeign
。客户将与不想在春季产生依赖性的旧应用程序部署,因此我直接使用OpenFeign而不是通过弹簧云。
我遇到了杰克逊编码器和身体类型的问题。似乎杰克逊编码器无法序列化接口实现到客户端方法的接口类型。例如,如果我的客户端方法是createfoo(foo interface)
其中foo
是一个接口,则使用create> createfoo(((fooimpl)fooimpl))
其中<代码> fooimpl 实现foo
接口,然后我得到一个编码器异常。
我已经创建了一个MCCE Gradle项目,该项目演示了问题在这里
客户定义是这样的:
public interface FooClient {
@RequestLine("POST /submit")
@Headers("Content-Type: application/json")
Response createFoo(Foo foo);
@RequestLine("POST /submit")
@Headers("Content-Type: application/json")
Response createFooImpl(FooImpl foo);
interface Foo { int id(); }
record FooImpl(int id) implements Foo { }
}
出现问题的失败测试是:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class FooClientTest {
@LocalServerPort int port;
@Test
public void clientTest() {
final FooClient lClient = Feign.builder()
.encoder(new JacksonEncoder(List.of(
// Possibly this would be necessary with the original encoder implementation.
// new FooModule()
)))
.target(FooClient.class, String.format("http://localhost:%s", port));
Response response = lClient.createFooImpl(new FooImpl(10));
assertThat(response.status()).isEqualTo(404);
response = lClient.createFoo(new FooImpl(10));
assertThat(response.status()).isEqualTo(404); // <<===== This fails with the exception below.
}
public static class FooModule extends SimpleModule {
{
addAbstractTypeMapping(Foo.class, FooImpl.class);
}
}
}
例外是:
feign.codec.EncodeException: No serializer found for class
codes.asm.feign.mcce.client.FooClient$FooImpl
and no properties discovered to create
BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
此问题是在此提交。它似乎以某种方式删除了杰克逊通过明确调用接口编码来映射接口的编码器的能力。
JavaType javaType = mapper.getTypeFactory().constructType(bodyType);
template.body(mapper.writerFor(javaType).writeValueAsBytes(object), Util.UTF_8);
基于某些实验,我认为原始代码可以正常工作,并有可能通过模块
对编码器进行某些配置。
如测试中所示,我可以通过在接口实现的情况下键入客户端方法来解决问题,但是在我的上下文中,由于多种原因,这是不希望的。
I'm working on implementing a Spring service and client and would like to use OpenFeign
for the client. The client will be deployed with legacy applications that do not want to incur a dependency on Spring, so I'm using OpenFeign directly instead of via Spring Cloud.
I've run into an issue with the Jackson encoder and the Body type. It seems that the Jackson encoder cannot serialize an interface implementation to an interface type for the client method. e.g. if my client method is createFoo(Foo interface)
where Foo
is an interface calling the method with createFoo((FooImpl)fooImpl)
where FooImpl
implements the Foo
interface then I get an encoder exception.
I've created an MCCE Gradle project demonstrating the issue here
The client definition is this:
public interface FooClient {
@RequestLine("POST /submit")
@Headers("Content-Type: application/json")
Response createFoo(Foo foo);
@RequestLine("POST /submit")
@Headers("Content-Type: application/json")
Response createFooImpl(FooImpl foo);
interface Foo { int id(); }
record FooImpl(int id) implements Foo { }
}
And the failing test demonstrating the issue is this:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class FooClientTest {
@LocalServerPort int port;
@Test
public void clientTest() {
final FooClient lClient = Feign.builder()
.encoder(new JacksonEncoder(List.of(
// Possibly this would be necessary with the original encoder implementation.
// new FooModule()
)))
.target(FooClient.class, String.format("http://localhost:%s", port));
Response response = lClient.createFooImpl(new FooImpl(10));
assertThat(response.status()).isEqualTo(404);
response = lClient.createFoo(new FooImpl(10));
assertThat(response.status()).isEqualTo(404); // <<===== This fails with the exception below.
}
public static class FooModule extends SimpleModule {
{
addAbstractTypeMapping(Foo.class, FooImpl.class);
}
}
}
The exception is:
feign.codec.EncodeException: No serializer found for class
codes.asm.feign.mcce.client.FooClient$FooImpl
and no properties discovered to create
BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
This issue was introduced in this commit. It seems to have somehow removed Jackson's ability to map the encoder for the interface to the implementation by explicitly calling for the interface encoder.
JavaType javaType = mapper.getTypeFactory().constructType(bodyType);
template.body(mapper.writerFor(javaType).writeValueAsBytes(object), Util.UTF_8);
Based on some experiments I think the original code would work fine, potentially with some configuration of the encoder via a Module
.
As demonstrated in the test, I can work around the issue by typing the client method with the interface implementation, but this is undesirable for a number of reasons in my context.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我已经找到了解决方法,但这很丑陋。创建一个
模块
,然后添加以下序列化器。我希望这会非常脆弱,很可能只放弃界面,并以DTO的具体记录定义进行。I've figured out a workaround, but it's quite ugly. Create a
Module
and add the following serializer. I expect this to be extremely brittle and will likely just abandon the interface and go with a concrete record definition as a DTO.