long类型前端丢失精度,可处理返回参数等情况
可通过注册消息解析器来解决,FastJsonHttpMessageConverter,MappingJackson2HttpMessageConverter等等。
/**
* 处理 long 精度丢失问题
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(this.fastJsonHttpMessageConverter());
}
@Bean
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
SerializeConfig serializeConfig = SerializeConfig.globalInstance;
serializeConfig.put(BigInteger.class, ToStringSerializer.instance);
serializeConfig.put(Long.class, ToStringSerializer.instance);
serializeConfig.put(Long.TYPE, ToStringSerializer.instance);
fastJsonConfig.setSerializeConfig(serializeConfig);
fastConverter.setFastJsonConfig(fastJsonConfig);
return fastConverter;
}
springboot + thymeleaf 后端通过Model与前端交互情况处理
上述情况同样会出现long类型精度丢失的情况,而且上述配置不会起作用,原因时springboot 解析 thymeleaf 模板时使用的序列化工具是独立创建的,与上述配置的序列化工具不同,我的解决方案时在自己项目的目录下创建JavaTimeModule,将自己自定义的参数写入其中。
路径:
public final class JavaTimeModule extends SimpleModule
{
private static final long serialVersionUID = 1L;
public JavaTimeModule()
{
super(PackageVersion.VERSION);
// First deserializers
// // Instant variants:
addDeserializer(Instant.class, InstantDeserializer.INSTANT);
addDeserializer(OffsetDateTime.class, InstantDeserializer.OFFSET_DATE_TIME);
addDeserializer(ZonedDateTime.class, InstantDeserializer.ZONED_DATE_TIME);
// // Other deserializers
addDeserializer(Duration.class, DurationDeserializer.INSTANCE);
addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE);
addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE);
addDeserializer(MonthDay.class, MonthDayDeserializer.INSTANCE);
addDeserializer(OffsetTime.class, OffsetTimeDeserializer.INSTANCE);
addDeserializer(Period.class, JSR310StringParsableDeserializer.PERIOD);
addDeserializer(Year.class, YearDeserializer.INSTANCE);
addDeserializer(YearMonth.class, YearMonthDeserializer.INSTANCE);
addDeserializer(ZoneId.class, JSR310StringParsableDeserializer.ZONE_ID);
addDeserializer(ZoneOffset.class, JSR310StringParsableDeserializer.ZONE_OFFSET);
// then serializers:
addSerializer(Duration.class, DurationSerializer.INSTANCE);
addSerializer(Instant.class, InstantSerializer.INSTANCE);
addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE);
addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE);
addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE);
addSerializer(MonthDay.class, MonthDaySerializer.INSTANCE);
addSerializer(OffsetDateTime.class, OffsetDateTimeSerializer.INSTANCE);
addSerializer(OffsetTime.class, OffsetTimeSerializer.INSTANCE);
addSerializer(Period.class, new ToStringSerializer(Period.class));
addSerializer(Year.class, YearSerializer.INSTANCE);
addSerializer(YearMonth.class, YearMonthSerializer.INSTANCE);
/* 27-Jun-2015, tatu: This is the real difference from the old
* {@link JSR310Module}: default is to produce ISO-8601 compatible
* serialization with timezone offset only, not timezone id.
* But this is configurable.
*/
addSerializer(ZonedDateTime.class, ZonedDateTimeSerializer.INSTANCE);
// note: actual concrete type is `ZoneRegion`, but that's not visible:
addSerializer(ZoneId.class, new ToStringSerializer(ZoneId.class));
addSerializer(ZoneOffset.class, new ToStringSerializer(ZoneOffset.class));
// key serializers
addKeySerializer(ZonedDateTime.class, ZonedDateTimeKeySerializer.INSTANCE);
// key deserializers
addKeyDeserializer(Duration.class, DurationKeyDeserializer.INSTANCE);
addKeyDeserializer(Instant.class, InstantKeyDeserializer.INSTANCE);
addKeyDeserializer(LocalDateTime.class, LocalDateTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(LocalDate.class, LocalDateKeyDeserializer.INSTANCE);
addKeyDeserializer(LocalTime.class, LocalTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(MonthDay.class, MonthDayKeyDeserializer.INSTANCE);
addKeyDeserializer(OffsetDateTime.class, OffsetDateTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(OffsetTime.class, OffsetTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(Period.class, PeriodKeyDeserializer.INSTANCE);
addKeyDeserializer(Year.class, YearKeyDeserializer.INSTANCE);
addKeyDeserializer(YearMonth.class, YearMonthKeyDeserializer.INSTANCE);
addKeyDeserializer(ZonedDateTime.class, ZonedDateTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(ZoneId.class, ZoneIdKeyDeserializer.INSTANCE);
addKeyDeserializer(ZoneOffset.class, ZoneOffsetKeyDeserializer.INSTANCE);
// 自定义的处理器
//JSON Long ==> String
addSerializer(BigInteger.class, ToStringSerializer.instance);
addSerializer(Long.class, ToStringSerializer.instance);
addSerializer(Long.TYPE, ToStringSerializer.instance);
}
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addValueInstantiators(new ValueInstantiators.Base() {
@Override
public ValueInstantiator findValueInstantiator(DeserializationConfig config,
BeanDescription beanDesc, ValueInstantiator defaultInstantiator)
{
JavaType type = beanDesc.getType();
Class<?> raw = type.getRawClass();
// 15-May-2015, tatu: In theory not safe, but in practice we do need to do "fuzzy" matching
// because we will (for now) be getting a subtype, but in future may want to downgrade
// to the common base type. Even more, serializer may purposefully force use of base type.
// So... in practice it really should always work, in the end. :)
if (ZoneId.class.isAssignableFrom(raw)) {
// let's assume we should be getting "empty" StdValueInstantiator here:
if (defaultInstantiator instanceof StdValueInstantiator) {
StdValueInstantiator inst = (StdValueInstantiator) defaultInstantiator;
// one further complication: we need ZoneId info, not sub-class
AnnotatedClass ac;
if (raw == ZoneId.class) {
ac = beanDesc.getClassInfo();
} else {
// we don't need Annotations, so constructing directly is fine here
// even if it's not generally recommended
ac = AnnotatedClassResolver.resolve(config,
config.constructType(ZoneId.class), config);
}
if (!inst.canCreateFromString()) {
AnnotatedMethod factory = _findFactory(ac, "of", String.class);
if (factory != null) {
inst.configureFromStringCreator(factory);
}
// otherwise... should we indicate an error?
}
// return ZoneIdInstantiator.construct(config, beanDesc, defaultInstantiator);
}
}
return defaultInstantiator;
}
});
}
protected AnnotatedMethod _findFactory(AnnotatedClass cls, String name, Class<?>... argTypes)
{
final int argCount = argTypes.length;
for (AnnotatedMethod method : cls.getFactoryMethods()) {
if (!name.equals(method.getName())
|| (method.getParameterCount() != argCount)) {
continue;
}
for (int i = 0; i < argCount; ++i) {
Class<?> argType = method.getParameter(i).getRawType();
if (!argType.isAssignableFrom(argTypes[i])) {
continue;
}
}
return method;
}
return null;
}
}
// springboot 解析 thymeleaf 过程中使用的序列化工具实例化过程 截取
1. StandardDialect.getExecutionAttributes
public Map<String, Object> getExecutionAttributes() {
final Map<String,Object> executionAttributes = new HashMap<String, Object>(5, 1.0f);
executionAttributes.put(
StandardExpressions.STANDARD_VARIABLE_EXPRESSION_EVALUATOR_ATTRIBUTE_NAME, getVariableExpressionEvaluator());
executionAttributes.put(
StandardExpressions.STANDARD_EXPRESSION_PARSER_ATTRIBUTE_NAME, getExpressionParser());
executionAttributes.put(
StandardExpressions.STANDARD_CONVERSION_SERVICE_ATTRIBUTE_NAME, getConversionService());
executionAttributes.put(
StandardSerializers.STANDARD_JAVASCRIPT_SERIALIZER_ATTRIBUTE_NAME, getJavaScriptSerializer());
executionAttributes.put(
StandardSerializers.STANDARD_CSS_SERIALIZER_ATTRIBUTE_NAME, getCSSSerializer());
return executionAttributes;
}
2. StandardDialect.getJavaScriptSerializer
public IStandardJavaScriptSerializer getJavaScriptSerializer() {
if (this.javaScriptSerializer == null) {
this.javaScriptSerializer = new StandardJavaScriptSerializer(true);
}
return this.javaScriptSerializer;
}
3.
public StandardJavaScriptSerializer(final boolean useJacksonIfAvailable) {
super();
IStandardJavaScriptSerializer newDelegate = null;
final String jacksonPrefix = (useJacksonIfAvailable? computeJacksonPackageNameIfPresent() : null);
if (jacksonPrefix != null) {
try {
newDelegate = new JacksonStandardJavaScriptSerializer(jacksonPrefix);
} catch (final Exception e) {
handleErrorLoggingOnJacksonInitialization(e);
} catch (final NoSuchMethodError e) {
handleErrorLoggingOnJacksonInitialization(e);
}
}
if (newDelegate == null) {
// Jackson could not be used, so we will use a default StandardJavaScriptSerializer (non-Jackson)
newDelegate = new DefaultStandardJavaScriptSerializer();
}
this.delegate = newDelegate;
}
4.
JacksonStandardJavaScriptSerializer(final String jacksonPrefix) {
super();
this.mapper = new ObjectMapper();
this.mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
this.mapper.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
this.mapper.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
this.mapper.getFactory().setCharacterEscapes(new JacksonThymeleafCharacterEscapes());
this.mapper.setDateFormat(new JacksonThymeleafISO8601DateFormat());
/*
* Now try to (conditionally) initialize support for Jackson serialization of JSR310 (java.time) objects,
* by making use of the 'jackson-datatype-jsr310' optional dependency.
*/
/**
* 我覆盖的类 com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
**/
final Class<?> javaTimeModuleClass =
ClassLoaderUtils.findClass(jacksonPrefix + ".datatype.jsr310.JavaTimeModule");
if (javaTimeModuleClass != null) {
// JSR310 support for Jackson is present in classpath
try {
this.mapper.registerModule((Module)javaTimeModuleClass.newInstance());
this.mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
} catch (final InstantiationException e) {
throw new ConfigurationException("Exception while trying to initialize JSR310 support for Jackson", e);
} catch (final IllegalAccessException e) {
throw new ConfigurationException("Exception while trying to initialize JSR310 support for Jackson", e);
}
}
}
狗急跳墙,见笑了。