在部分场景中,后台的时间属性用的不是Date或Long,而是Instant,Java8引入的一个精度极高的时间类型,可以精确到纳秒,但实际使用的时候不需要这么高的精确度,通常到毫秒就可以了。
而在前后端传参的时候需要对Instant类型进行序列化及反序列化等处理,默认情况下,ObjectMapper是不支持序列化Instant类型的,需要注册JavaTimeModule
才行,而且序列化的结果也不是时间戳,测试如下
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.time.Instant;
/**
* Instant Jackson测试
*
* @author yangguirong
*/
@Slf4j
public class InstantTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void serializeTest() throws JsonProcessingException {
objectMapper.registerModule(new JavaTimeModule());
String str = objectMapper.writeValueAsString(Instant.now());
log.info("serializeTest: {}", str);
// serializeTest: 1691208180.052185000
}
@Test
void deserializeTest() throws JsonProcessingException {
objectMapper.registerModule(new JavaTimeModule());
Instant instant = objectMapper.readValue("1691208180.052185000", Instant.class);
log.info("deserializeTest: {}", instant);
// deserializeTest: 2023-08-05T04:03:00.052185Z
}
}
想要将其序列化为毫秒时间戳,需要对序列化及反序列化进行自定义
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.time.Instant;
/**
* 自定义Instant序列化及反序列
*
* @author yangguirong
*/
public class InstantMillsTimeModule extends SimpleModule {
public InstantMillsTimeModule() {
this.addSerializer(Instant.class, new InstantMillisecondsSerializer());
this.addDeserializer(Instant.class, new InstantMillisecondsDeserializer());
}
public static class InstantMillisecondsSerializer extends JsonSerializer<Instant> {
@Override
public void serialize(Instant instant, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (instant == null) {
jsonGenerator.writeNull();
} else {
jsonGenerator.writeNumber(instant.toEpochMilli());
}
}
}
@Slf4j
public static class InstantMillisecondsDeserializer extends JsonDeserializer<Instant> {
@Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
try {
long mills = p.getValueAsLong();
return mills > 0 ? Instant.ofEpochMilli(mills) : null;
} catch (Exception e) {
log.error("Instant类型反序列化失败!val: {}, message: {}", p.getText(), e.getMessage());
}
return null;
}
}
}
再来测试一下自定义的序列化及反序列化方式
import com.example.websocket.config.InstantMillsTimeModule;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.time.Instant;
/**
* Instant Jackson测试
*
* @author yangguirong
*/
@Slf4j
public class InstantTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void serializeTest() throws JsonProcessingException {
objectMapper.registerModule(new JavaTimeModule());
String str = objectMapper.writeValueAsString(Instant.now());
log.info("serialize: {}", str);
// serialize: 1691208180.052185000
}
@Test
void deserializeTest() throws JsonProcessingException {
objectMapper.registerModule(new JavaTimeModule());
Instant instant = objectMapper.readValue("1691208180.052185000", Instant.class);
log.info("deserialize: {}", instant);
// deserialize: 2023-08-05T04:03:00.052185Z
}
@Test
void millsSerializeTest() throws JsonProcessingException {
objectMapper.registerModule(new InstantMillsTimeModule());
String str = objectMapper.writeValueAsString(Instant.now());
log.info("millsSerializeTest: {}", str);
// millsSerializeTest: 1691208541018
}
@Test
void millsDeserializeTest() throws JsonProcessingException {
objectMapper.registerModule(new InstantMillsTimeModule());
Instant instant = objectMapper.readValue("1691208541018", Instant.class);
log.info("millsDeserializeTest: {}", instant);
// deserialize: 2023-08-05T04:09:01.018Z
}
}
可以看到,结果是符合预期的,可以在毫秒时间戳和Instant之间相互转换。
在后台配置SpringBoot的时候,需要考虑两种情况,一种就是Instant作为RequestParam/PathVariable
的情况,另一种是RequestBody/ResponseBody
的情况。前者借助转换器实现,配置如下
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.time.Instant;
/**
* web mvc设置
*
* @author yangguirong
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(instantConvert());
}
public Converter<String, Instant> instantConvert() {
// 不能替换为lambda表达式
return new Converter<String, Instant>() {
@Override
public Instant convert(String source) {
if (StringUtils.hasText(source)) {
return Instant.ofEpochMilli(Long.parseLong(source));
}
return null;
}
};
}
}
后者如果是局部配置,则在具体的实体类属性上添加@JsonSerialize
和@JsonDeserialize
注解,在注解中指定序列化器和反序列化器即可。如果是全局配置,则可以按照如下方式进行配置,将InstantMillsTimeModule
注册为Bean,这个Bean会在JacksonAutoConfiguration
中的StandardJackson2ObjectMapperBuilderCustomizer
被自动注入,然后进行注册。
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Jackson配置
*
* @author yangguirong
*/
@Configuration
@AutoConfigureBefore(JacksonAutoConfiguration.class)
public class JacksonCustomizerConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonModuleRegistryCustomizer() {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToDisable(
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, SerializationFeature.FAIL_ON_EMPTY_BEANS
);
}
@Bean
public InstantMillsTimeModule instantMillsTimeModule() {
return new InstantMillsTimeModule();
}
}
简单的接口测试
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
/**
* instant测试
*
* @author yangguirong
*/
@Slf4j
@RequestMapping("instant")
@RestController
public class InstantTestController {
@GetMapping("getInstant")
public Instant getInstant() {
return Instant.now();
}
@PutMapping("setInstant")
public void setInstant(@RequestParam Instant instant) {
log.info("setInstant: {}", instant);
}
@GetMapping("getInstantDemoVO")
public DemoVO getInstantDemoVO() {
return new DemoVO(Instant.now());
}
@PutMapping("setInstantDemoVO")
public void setInstantDemoVO(@RequestBody DemoVO vo) {
log.info("setInstantDemoVO:{}", vo);
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class DemoVO {
private Instant instant;
}
}