如何在Feign Client中以特定格式序列化请求体中的日期?

huangapple go评论86阅读模式
英文:

How to serialise Date of request body in a specific format in Feign Client?

问题

To configure the serialization of the date format in Feign Client, you can make the following adjustments to your code:

  1. Update your MyFeignConfig class to properly configure the ObjectMapper with the desired date format:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyFeignConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
        return objectMapper;
    }
    
    @Bean
    public ResponseEntityDecoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter(objectMapper())));
    }
}
  1. Modify your MyFeignClient interface to use the updated MyFeignConfig class as configuration:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;

@FeignClient(value = "myfeign", url = "https://myfeign.com/", configuration = MyFeignConfig.class)
public interface MyFeignClient {

    @PostMapping(value = "/myfeign/", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    MyResponse sendRequest(MyRequest request);
}

With these changes, Feign should now serialize the date in the format "yyyy-MM-dd'T'HH:mm:ss.SSSZ" as you specified in your MyRequest class.

英文:

I try to send a request to a third party api using feign client. When I check the request body, it looks like below:

{
  "requestTime": "2023-06-07T12:18:00.916+00:00"
}

but the api only accept the date format yyyy-MM-dd'T'mm:hh:ss.SSSZ, so the valid request body would be something similar below:

{
  "requestTime": "2023-06-17T14:53:47.402Z"
}

How do I config the serialisation of date format in Feign Client?

My codes:

@FeignClient(value = "myfeign", url = "https://myfeign.com/")
public interface MyFeignClient {

    @PostMapping(value = "/myfeign/", produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_JSON_VALUE)
    MyResponse sendRequest(MyRequest request);
}

And MyRequest is generated from openapi-generator.

public class MyRequest {
   @JsonProperty("requestTime")
   @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
   private Date requestTime;
   // getter...
}

Edit: My approach below doesn't work as the request object still use "2023-06-07T12:18:00.916+00:00" format.

public class MyFeignConfig {

    @Bean
    public Encoder feignEncoder() {
        HttpMessageConverter<Object> jacksonConverter = new MappingJackson2HttpMessageConverter(objectMapper());

        HttpMessageConverters httpMessageConverters = new HttpMessageConverters(jacksonConverter);
        ObjectFactory<HttpMessageConverters> objectFactory = () -> httpMessageConverters;


        return new SpringEncoder(objectFactory);
    }

    private ObjectMapper objectMapper() {
        final String DATE_FORMAT = "yyyy-MM-dd'T'mm:hh:ss.SSSZ";
        SimpleDateFormat dateFormat = new SimpleDateFormat((DATE_FORMAT));
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(dateFormat);
    }

add configuration in the client

@FeignClient(value = "myfeign", url = "https://myfeign.com/",  configuration = "MyFeignConfig.class")
public interface MyFeignClient {

    @PostMapping(value = "/myfeign/", produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_JSON_VALUE)
    MyResponse sendRequest(MyRequest request);
}

答案1

得分: 2

Here are the translated parts of the content you provided:

My approach below doesn't work

我的下面的方法不起作用

jackson/databind ObjectMapper#setDateFormat() takes a DateFormat class, not a string.

jackson/databindObjectMapper#setDateFormat()接受一个DateFormat类,而不是一个字符串。

See also "Jackson Date" by Eugen Baeldung for illustration.

也可以参考Eugen Baeldung的“Jackson日期”进行说明。

Something like:

类似于:

public class MyFeignConfig {

    private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

    @Bean
    public Encoder feignEncoder() {
        ObjectMapper objectMapper = objectMapper();
        HttpMessageConverter<Object> jacksonConverter = new MappingJackson2HttpMessageConverter(objectMapper);

        HttpMessageConverters httpMessageConverters = new HttpMessageConverters(jacksonConverter);
        ObjectFactory<HttpMessageConverters> objectFactory = () -> httpMessageConverters;

        return new SpringEncoder(objectFactory);
    }

    private ObjectMapper objectMapper() {
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(dateFormat);
        return objectMapper;
    }
}

注意日期格式字符串:它已更改为"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",因为Java中的日期格式中的mmhh代表上午/下午的分钟和小时,您应该使用HH表示一天中的小时(0 - 23)和mm表示小时中的分钟。

As noted by Olivier in the comments, 'Z' will output a literal 'Z' in the output, as opposed to Z, which prints the timezone offset to UTC, as illustrated in this question.

正如评论中Olivier所指出的,'Z'会在输出中输出字面的'Z',而不是时区偏移量到UTC,正如在这个问题中所示。

Then, you apply this configuration to your feign client:

然后,您将此配置应用于您的Feign客户端:

@FeignClient(value = "myfeign", url = "https://myfeign.com/", configuration = MyFeignConfig.class)
public interface MyFeignClient {

    @PostMapping(value = "/myfeign/", produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_JSON_VALUE)
    MyResponse sendRequest(MyRequest request);
}

This should configure your Feign client to use your custom ObjectMapper which uses the correct date format. The time zone has been set to UTC in the SimpleDateFormat to make sure the serialized date string ends with a Z to indicate UTC.

这应该会配置您的Feign客户端使用您的自定义ObjectMapper,该ObjectMapper使用正确的日期格式。SimpleDateFormat中已将时区设置为UTC,以确保序列化的日期字符串以Z结尾,表示UTC。

I updated my approach for missing codes. So, I don't understand why my approach still send as "2023-06-07T12:18:00.916+00:00".
What I expected actually is fail to serialize and throw error. Could you explain this to me?

我更新了缺失的代码部分。所以,我不明白为什么我的方法仍然发送为"2023-06-07T12:18:00.916+00:00"。实际上,我期望的是序列化失败并抛出错误。您能解释一下吗?

When Spring Boot starts up, it creates a default ObjectMapper bean with standard configuration. If you define an ObjectMapper in your own configuration (like you could have done in MyFeignConfig), Spring does not replace the default one - it simply adds your ObjectMapper as another bean.

当Spring Boot启动时,它会创建一个具有标准配置的默认ObjectMapper bean。如果您在自己的配置中定义了一个ObjectMapper(就像您在MyFeignConfig中可能做的那样),Spring不会替换默认的ObjectMapper - 它只是将您的ObjectMapper添加为另一个bean。

Since SpringEncoder (the class used by Feign for encoding requests) is configured to use an ObjectMapper bean, it will inject one from the application context. But if there are multiple ObjectMapper beans, the one it gets might not be the one you have configured. This could explain why you are seeing the default date format instead of the one you have set.

由于SpringEncoder(Feign用于编码请求的类)配置为使用ObjectMapper bean,因此它将从应用程序上下文中注入一个。但如果有多个ObjectMapper bean,它获取的可能不是您配置的那个。这可能解释了为什么您看到默认的日期格式而不是您设置的日期格式。

One way to ensure your ObjectMapper is used is by marking it as @Primary. This tells Spring that when multiple beans of the same type are present, this is the one that should be used.

确保使用您的ObjectMapper的一种方法是将其标记为@Primary。这告诉Spring,当存在多个相同类型的bean时,应使用此bean。

You can update your MyFeignConfig class as follows:

您可以按照以下方式更新您的MyFeignConfig类:

public class MyFeignConfig {

    private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(dateFormat);
        return objectMapper;
    }

    @Bean
    public Encoder feignEncoder(ObjectMapper objectMapper) {
        HttpMessageConverter<Object> jacksonConverter = new MappingJackson2HttpMessageConverter(objectMapper);

        HttpMessageConverters httpMessageConverters = new HttpMessageConverters(jacksonConverter);
        ObjectFactory<HttpMessageConverters> objectFactory = () -> httpMessageConverters;

        return new SpringEncoder(objectFactory);
    }
}

注意,ObjectMapper bean现在标记为@Primary,feignEncoder方法现在接受一个ObjectMapper参数。这确保了在feignEncoder中使用的ObjectMapper与您配置的ObjectMapper相同。

英文:

> My approach below doesn't work

Without knowing what "doesn't work" means, I can at least see that jackson/databind ObjectMapper#setDateFormat() takes a DateFormat class, not a string.

See also "Jackson Date" by Eugen Baeldung for illustration.

Something like:

public class MyFeignConfig {

    private static final String DATE_FORMAT = &quot;yyyy-MM-dd&#39;T&#39;HH:mm:ss.SSS&#39;Z&#39;&quot;;

    @Bean
    public Encoder feignEncoder() {
        ObjectMapper objectMapper = objectMapper();
        HttpMessageConverter&lt;Object&gt; jacksonConverter = new MappingJackson2HttpMessageConverter(objectMapper);

        HttpMessageConverters httpMessageConverters = new HttpMessageConverters(jacksonConverter);
        ObjectFactory&lt;HttpMessageConverters&gt; objectFactory = () -&gt; httpMessageConverters;

        return new SpringEncoder(objectFactory);
    }

    private ObjectMapper objectMapper() {
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
        dateFormat.setTimeZone(TimeZone.getTimeZone(&quot;UTC&quot;));
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(dateFormat);
        return objectMapper;
    }
}

Note the date format string: it is changed to &quot;yyyy-MM-dd&#39;T&#39;HH:mm:ss.SSS&#39;Z&#39;&quot;, because mm and hh in Java's date format stands for minute and hour in am/pm, you should use HH for hour of day (0 - 23) and mm for minute in hour.

As noted by Olivier in the comments, &#39;Z&#39; will output a literal Z in the output, as opposed to Z, which prints the timezone offset to UTC, as illustrated in this question.

Then, you apply this configuration to your feign client:

@FeignClient(value = &quot;myfeign&quot;, url = &quot;https://myfeign.com/&quot;, configuration = MyFeignConfig.class)
public interface MyFeignClient {

    @PostMapping(value = &quot;/myfeign/&quot;, produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_JSON_VALUE)
    MyResponse sendRequest(MyRequest request);
}

This should configure your Feign client to use your custom ObjectMapper which uses the correct date format. The time zone has been set to UTC in the SimpleDateFormat to make sure the serialized date string ends with a Z to indicate UTC.


> I updated my approach for missing codes. So, I don't understand why my approach still send as "2023-06-07T12:18:00.916+00:00".
What I expected actually is fail to serialize and throw error. Could you explain this to me?

When Spring Boot starts up, it creates a default ObjectMapper bean with standard configuration. If you define an ObjectMapper in your own configuration (like you could have done in MyFeignConfig), Spring does not replace the default one - it simply adds your ObjectMapper as another bean.

Since SpringEncoder (the class used by Feign for encoding requests) is configured to use an ObjectMapper bean, it will inject one from the application context. But if there are multiple ObjectMapper beans, the one it gets might not be the one you have configured. This could explain why you are seeing the default date format instead of the one you have set.

One way to ensure your ObjectMapper is used is by marking it as @Primary. This tells Spring that when multiple beans of the same type are present, this is the one that should be used.

You can update your MyFeignConfig class as follows:

public class MyFeignConfig {

    private static final String DATE_FORMAT = &quot;yyyy-MM-dd&#39;T&#39;HH:mm:ss.SSS&#39;Z&#39;&quot;;

    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
        dateFormat.setTimeZone(TimeZone.getTimeZone(&quot;UTC&quot;));
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(dateFormat);
        return objectMapper;
    }

    @Bean
    public Encoder feignEncoder(ObjectMapper objectMapper) {
        HttpMessageConverter&lt;Object&gt; jacksonConverter = new MappingJackson2HttpMessageConverter(objectMapper);

        HttpMessageConverters httpMessageConverters = new HttpMessageConverters(jacksonConverter);
        ObjectFactory&lt;HttpMessageConverters&gt; objectFactory = () -&gt; httpMessageConverters;

        return new SpringEncoder(objectFactory);
    }
}

Note that the ObjectMapper bean is now annotated with @Primary, and the feignEncoder method now takes an ObjectMapper parameter. This ensures that the ObjectMapper used in feignEncoder is the same one that you have configured.
See "Spring Boot: Customize the Jackson ObjectMapper", again by Eugen Baeldung.

Warning: Marking a bean as @Primary will affect all places in your application where an ObjectMapper bean is injected.
If you want to use different ObjectMapper configurations in different parts of your application, you will need a different solution. In that case, you might need to create a custom Encoder or Decoder that uses your specific ObjectMapper, or use Feign's request/response interceptors to manually handle the serialization.


For more, the Spring Cloud OpenFeign documentation explains how you can take full control of the feign client by declaring additional configuration using @FeignClient. In this case, the client is composed from the components already in FeignClientsConfiguration together with any in your custom configuration (where the latter will override the former). The FeignClientsConfiguration class provides default beans for feign including feign.Encoder which is SpringEncoder by default.

If there are multiple ObjectMapper beans in the application context, the one that SpringEncoder (used by Feign for encoding requests) gets might not be the one you have configured. This situation might arise if the custom configuration is included in a @ComponentScan that would otherwise include this configuration as it will become the default source for feign.Decoder, feign.Encoder, feign.Contract, etc., when specified. This can be avoided by putting it in a separate, non-overlapping package from any @ComponentScan or @SpringBootApplication, or it can be explicitly excluded in @ComponentScan.

From the source code of JacksonEncoder, which is an implementation of Encoder, you can see that an ObjectMapper is used to encode the request body. It uses the ObjectMapper to write the object as bytes and sets it as the body of the RequestTemplate. This suggests that ObjectMapper plays an important role in the encoding process, and if the ObjectMapper is not properly configured, it can lead to unexpected results, such as the one you are experiencing.

答案2

得分: 0

要配置Feign客户端中的日期格式序列化,您可以按照以下步骤进行:

创建一个配置类,我们称其为FeignConfig,并用 @Configuration 注解进行标注。

@Configuration
public class FeignConfig {
    // 配置代码将放在这里
}

在FeignConfig类中为ObjectMapper定义一个bean。该bean将负责配置日期格式序列化。

@Bean
public ObjectMapper objectMapper() {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setDateFormat(dateFormat);
    
    return objectMapper;
}

在这里,我们设置了所需的日期格式并确保它使用UTC时区。

在FeignConfig类中为Encoder定义一个bean。该bean将使用自定义的ObjectMapper进行序列化。

@Bean
public Encoder feignEncoder() {
    HttpMessageConverter<Object> jacksonConverter = new MappingJackson2HttpMessageConverter(objectMapper());

    return new SpringEncoder(() -> new HttpMessageConverters(jacksonConverter));
}

通过在@FeignClient注解中指定configuration属性,将FeignConfig应用于您的Feign客户端。

@FeignClient(value = "myfeign", url = "https://myfeign.com/", configuration = FeignConfig.class)
public interface MyFeignClient {
    // Feign客户端方法
}

通过这样做,Feign客户端将使用指定日期格式的自定义ObjectMapper进行序列化,确保在将请求发送到第三方API之前正确格式化请求体。

英文:

To configure the serialization of the date format in Feign Client, you can follow these steps:

Create a configuration class, let's call it FeignConfig, and annotate it with @Configuration.

@Configuration
public class FeignConfig {
    // Configuration code will go here
}

Define a bean for the ObjectMapper in the FeignConfig class. This bean will be responsible for configuring the date format serialization.

@Bean
public ObjectMapper objectMapper() {
    SimpleDateFormat dateFormat = new SimpleDateFormat(&quot;yyyy-MM-dd&#39;T&#39;HH:mm:ss.SSS&#39;Z&#39;&quot;);
    dateFormat.setTimeZone(TimeZone.getTimeZone(&quot;UTC&quot;));
    
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setDateFormat(dateFormat);
    
    return objectMapper;
}

Here, we're setting the desired date format and ensuring that it uses the UTC time zone.

Create a bean for the Encoder in the FeignConfig class. This bean will use the customized ObjectMapper for serialization.

@Bean
public Encoder feignEncoder() {
    HttpMessageConverter&lt;Object&gt; jacksonConverter = new MappingJackson2HttpMessageConverter(objectMapper());

    return new SpringEncoder(() -&gt; new HttpMessageConverters(jacksonConverter));
}
Apply the FeignConfig to your Feign client by specifying the configuration attribute in the @FeignClient annotation.
@FeignClient(value = &quot;myfeign&quot;, url = &quot;https://myfeign.com/&quot;, configuration = FeignConfig.class)
public interface MyFeignClient {
    // Feign client methods
}

By doing this, the Feign client will use the customized ObjectMapper with the specified date format for serialization, ensuring that the request body is formatted correctly before sending the request to the third-party API.

huangapple
  • 本文由 发表于 2023年6月8日 22:14:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76432777.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定