当前位置: 首页 > news >正文

Spring Boot使用LocalDateTime、LocalDate作为入参

0x0 背景

项目中使用LocalDateTime系列作为dto中时间的类型,但是spring收到参数后总报错,为了全局配置时间类型转换,尝试了如下3中方法。

注:本文基于Springboot2.0测试,如果无法生效可能是spring版本较低导致的。PS:如果你的Controller中的LocalDate类型的参数啥注解(RequestParam、PathVariable等)都没加,也是会出错的,因为默认情况下,解析这种参数使用ModelAttributeMethodProcessor进行处理,而这个处理器要通过反射实例化一个对象出来,然后再对对象中的各个参数进行convert,但是LocalDate类没有构造函数,无法反射实例化因此会报错!!!

0x1 当LocalDateTime作为RequestParam或者PathVariable时

这种情况要和时间作为Json字符串时区别对待,因为前端json转后端pojo底层使用的是Json序列化Jackson工具(HttpMessgeConverter);而时间字符串作为普通请求参数传入时,转换用的是Converter,两者有区别哦。 在这种情况下,有如下几种方案:

1. 使用Converter

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.converter.HttpMessageConverter;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;@Configuration
public class DateConfig {@Beanpublic Converter<String, LocalDate> localDateConverter() {return new Converter<>() {@Overridepublic LocalDate convert(String source) {return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd"));}};}@Beanpublic Converter<String, LocalDateTime> localDateTimeConverter() {return new Converter<>() {@Overridepublic LocalDateTime convert(String source) {return LocalDateTime.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));}};}}

以上两个bean会注入到spring mvc的参数解析器(好像叫做ParameterConversionService),当传入的字符串要转为LocalDateTime类时,spring会调用该Converter对这个入参进行转换。

注意:关于自定义的参数转换器 Converter,这里遇到了一个坑,再这里详细记录下,本来想法是为了代码精简,将上面匿名内部类的写法精简成lambda表达式的方式:

    @Bean@ConditionalOnBean(name = "requestMappingHandlerAdapter")public Converter<String, LocalDate> localDateConverter() {return source -> LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT));}

当我再次启动项目时却出现了异常:

Caused by: java.lang.IllegalArgumentException: Unable to determine source type <S> and target type <T> for your Converter [com.example.demo126.config.MappingConverterAdapter$$Lambda$522/817994751]; does the class parameterize those types?

 百思不得其解,在查阅了资料才得知一二。

web项目启动注册requestMappingHandlerAdapter的时候会初始化WebBindingInitializer

adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());

ConfigurableWebBindingInitializer需要FormattingConversionService, 而FormattingConversionService会将所有的Converter添加进来,添加的时候需要获取泛型信息:

@Override
public void addFormatters(FormatterRegistry registry) {for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {registry.addConverter(converter);}for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {registry.addConverter(converter);}for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {registry.addFormatter(formatter);}
}

添加Converter.class 一般是通过接口获取两个泛型的具体类型

public ResolvableType as(Class<?> type) {if (this == NONE) {return NONE;}Class<?> resolved = resolve();if (resolved == null || resolved == type) {return this;}for (ResolvableType interfaceType : getInterfaces()) {ResolvableType interfaceAsType = interfaceType.as(type);if (interfaceAsType != NONE) {return interfaceAsType;}}return getSuperType().as(type);
}


Lambda表达式的接口是Converter,并不能得到具体的类型,在窥探了SpringMVC源码后才得知原来如此,既然指导了原因,那解决办法:

  • 最简单的方法就是不适用Lambda表达式,还是老老实实的使用匿名内部类,这样就不会存在上述问题

  • 或者就是等requestMappingHandlerAdapterbean注册完成之后再添加自己的converter就不会注册到FormattingConversionService

@Bean
@ConditionalOnBean(name = "requestMappingHandlerAdapter")
public Converter<String, LocalDateTime> localDateTimeConverter() {return source -> LocalDateTime.parse(source, DateTimeUtils.DEFAULT_FORMATTER);
}

还可以对前端传递的string进行正则匹配,如yyyy-MM-dd HH:mm:ss、yyyy-MM-dd、 HH:mm:ss等,进行匹配。以适应多种场景。

@Component
public class DateConverter implements Converter<String, Date> {@Overridepublic Date convert(String value) {/*** 可对value进行正则匹配,支持日期、时间等多种类型转换* 这里我偷个懒,在匹配Date日期格式时直接使用了 hutool 为我们已经写好的解析工具类,这里就不重复造轮子了* cn.hutool.core.date.DateUtil* @param value* @return*/return DateUtil.parse(value.trim());}
}

 注:这里偷个懒,在匹配Date日期格式时直接使用了 hutool 已经写好的解析工具类,这里就不重复造轮子了,下面的方法同样使用了该工具类,想要在自己的项目中使用该工具类也很简单,在项目pom文件中引入hutool的依赖就可以了。

2. 使用ControllerAdvice配合initBinder

@ControllerAdvice
public class GlobalExceptionHandler {@InitBinderprotected void initBinder(WebDataBinder binder) {binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd")));}});binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}});binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalTime.parse(text, DateTimeFormatter.ofPattern("HH:mm:ss")));}});}
}

从名字就可以看出来,这是在controller做环切(这里面还可以全局异常捕获),在参数进入handler之前进行转换;转换为相应的对象。

0x2 当LocalDateTime作为Json形式传入

这种情况下,如同上文描述,要利用Jackson的json序列化和反序列化来做:

@Configuration
public class JacksonConfig {/** 默认日期时间格式 */public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";/** 默认日期格式 */public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";/** 默认时间格式 */public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";@Beanpublic ObjectMapper objectMapper(){ObjectMapper objectMapper = new ObjectMapper();//objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);//objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);JavaTimeModule javaTimeModule = new JavaTimeModule();javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));objectMapper.registerModule(javaTimeModule).registerModule(new ParameterNamesModule());return objectMapper;}}

0x3 当LocalDateTime作为Json形式传入 

import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.TimeZone;@Configuration
public class DateConfig {@Beanpublic Jackson2ObjectMapperBuilderCustomizer customizer() {return builder -> {builder.locale(Locale.CHINA);builder.timeZone(TimeZone.getTimeZone("GMT+8:00"));builder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN);JavaTimeModule javaTimeModule = new JavaTimeModule();javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));builder.modules(javaTimeModule);};}}

 0x4 来个完整的配置

package com.fly.hi.common.config;import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;@Configuration
public class DateConfig {/** 默认日期时间格式 */public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";/** 默认日期格式 */public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";/** 默认时间格式 */public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";/*** LocalDate转换器,用于转换RequestParam和PathVariable参数*/@Beanpublic Converter<String, LocalDate> localDateConverter() {return new Converter<>() {@Overridepublic LocalDate convert(String source) {return LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT));}};}/*** LocalDateTime转换器,用于转换RequestParam和PathVariable参数*/@Beanpublic Converter<String, LocalDateTime> localDateTimeConverter() {return new Converter<>() {@Overridepublic LocalDateTime convert(String source) {return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT));}};}/*** LocalTime转换器,用于转换RequestParam和PathVariable参数*/@Beanpublic Converter<String, LocalTime> localTimeConverter() {return new Converter<>() {@Overridepublic LocalTime convert(String source) {return LocalTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT));}};}/*** Date转换器,用于转换RequestParam和PathVariable参数*/@Beanpublic Converter<String, Date> dateConverter() {return new Converter<>() {@Overridepublic Date convert(String source) {SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);try {return format.parse(source);} catch (ParseException e) {throw new RuntimeException(e);}}};}/*** Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json*/@Beanpublic ObjectMapper objectMapper(){ObjectMapper objectMapper = new ObjectMapper();objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);//LocalDateTime系列序列化和反序列化模块,继承自jsr310,我们在这里修改了日期格式JavaTimeModule javaTimeModule = new JavaTimeModule();javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));//Date序列化和反序列化javaTimeModule.addSerializer(Date.class, new JsonSerializer<>() {@Overridepublic void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);String formattedDate = formatter.format(date);jsonGenerator.writeString(formattedDate);}});javaTimeModule.addDeserializer(Date.class, new JsonDeserializer<>() {@Overridepublic Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);String date = jsonParser.getText();try {return format.parse(date);} catch (ParseException e) {throw new RuntimeException(e);}}});objectMapper.registerModule(javaTimeModule);return objectMapper;}
}

 0x5 深入研究SpringMVC数据绑定过程

写一个简单controller,下个断点看看方法调用栈:

@GetMapping("/getDate")
public LocalDateTime getDate(@RequestParam LocalDate date,@RequestParam LocalDateTime dateTime,@RequestParam Date originalDate) {System.out.println(date);System.out.println(dateTime);System.out.println(originalDate);return LocalDateTime.now();
}

断住以后,看下方法调用栈中一些关键方法:

//进入DispatcherServlet
doService:942, DispatcherServlet
//处理请求
doDispatch:1038, DispatcherServlet
//生成调用链(前处理、实际调用方法、后处理)
handle:87, AbstractHandlerMethodAdapter
//反射获取到实际调用方法,准备开始调用
invokeHandlerMethod:895, RequestMappingHandlerAdapter
invokeAndHandle:102, ServletInvocableHandlerMethod
//这里是关键,参数从这里开始获取到
invokeForRequest:142, InvocableHandlerMethod
doInvoke:215, InvocableHandlerMethod
//这个是Java reflect调用,因此一定是在这之前获取到的参数
invoke:566, Method

根据上述分析,发现invokeForRequest:142, InvocableHandlerMethod这里的代码是用来拿到实际参数的:

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {//这个方法是获取参数的,在这里下个断Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}//这里开始调用方法return doInvoke(args);
}

进入这个方法看看是什么操作:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {//获取方法参数数组,包含了入参信息,比如类型、泛型等等MethodParameter[] parameters = getMethodParameters();//这个用来存放一会从request parameter转换的参数Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);//这里看起来没啥卵用(providedArgs为空)args[i] = resolveProvidedArgument(parameter, providedArgs);//这里开始获取到方法实际调用的参数,步进if (this.argumentResolvers.supportsParameter(parameter)) {//从名字就看出来:参数解析器解析参数args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);continue;}}return args;
}

进入resolveArgument看看:

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {//根据方法入参,获取对应的解析器HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);//开始解析参数(把请求中的parameter转为方法的入参)return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

这里根据参数获取相应的参数解析器,看看内部如何获取的:

//遍历,调用supportParameter方法,跟进看看
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {if (methodArgumentResolver.supportsParameter(parameter)) {result = methodArgumentResolver;this.argumentResolverCache.put(parameter, result);break;}
}

这里,遍历参数解析器,查找有没有适合的解析器!那么,有哪些参数解析器呢(我测试的时候有26个)???我列出几个重要的看看,是不是很眼熟!!!

{RequestParamMethodArgumentResolver@7686} 
{PathVariableMethodArgumentResolver@8359} 
{RequestResponseBodyMethodProcessor@8366} 
{RequestPartMethodArgumentResolver@8367} 

进入最常用的一个解析器看看它的supportsParameter方法,发现就是通过参数注解来获取相应的解析器的。

public boolean supportsParameter(MethodParameter parameter) {//如果参数拥有注解@RequestParam,则走这个分支(知道为什么上文要对RequestParam和Json两种数据区别对待了把)if (parameter.hasParameterAnnotation(RequestParam.class)) {//这个似乎是对Optional类型的参数进行处理的if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);return (requestParam != null && StringUtils.hasText(requestParam.name()));}else {return true;}}//......
}

也就是说,对于@RequestParam和@RequestBody以及@PathVariable注解的参数,SpringMVC会使用不同的参数解析器进行数据绑定!那么,这三种解析器分别使用什么Converter解析参数呢?分别进入三种解析器看一看:首先看下RequestParamMethodArgumentResolver发现内部使用WebDataBinder进行数据绑定,底层使用的是ConversionService (也就是Converter注入的地方)

WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
//通过DataBinder进行数据绑定的
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
//跟进convertIfNecessary()
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,@Nullable MethodParameter methodParam) throws TypeMismatchException {return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}
//继续跟进,看到了把
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {try {return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);}catch (ConversionFailedException ex) {// fallback to default conversion logic belowconversionAttemptEx = ex;}}
}

然后看下RequestResponseBodyMethodProcessor发现使用的转换器是HttpMessageConverter类型的:

//resolveArgument方法内部调用下面进行参数解析
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());//step into readWithMessageConverters(),我们看到这里的Converter是HttpMessageConverter
for (HttpMessageConverter<?> converter : this.messageConverters) {Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();GenericHttpMessageConverter<?> genericConverter =(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :(targetClass != null && converter.canRead(targetClass, contentType))) {if (message.hasBody()) {HttpInputMessage msgToUse =getAdvice().beforeBodyRead(message, parameter, targetType, converterType);body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);}else {body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);}break;}
}

最后看下PathVariableMethodArgumentResolver发现 和RequestParam走的执行路径一致(二者都是继承自AbstractNamedValueMethodArgumentResolver解析器),因此代码就不贴了。

0xFF总结

如果要转换request传来的参数到指定的类型,根据入参注解要进行区分:

  1. 如果是RequestBody,那么通过配置ObjectMapper(这个玩意儿会注入到Jackson的HttpMessagConverter里面,即MappingJackson2HttpMessageConverter中)来实现Json格式数据的序列化和反序列化;

  2. 如果是RequestParam或者PathVariable类型的参数,通过配置Converter实现参数转换(这些Converter会注入到ConversionService中)。

相关文章:

Spring Boot使用LocalDateTime、LocalDate作为入参

0x0 背景 项目中使用LocalDateTime系列作为dto中时间的类型&#xff0c;但是spring收到参数后总报错&#xff0c;为了全局配置时间类型转换&#xff0c;尝试了如下3中方法。 注&#xff1a;本文基于Springboot2.0测试&#xff0c;如果无法生效可能是spring版本较低导致的。PS&…...

第七周第七天学习总结 | MySQL入门及练习学习第二天

实操练习&#xff1a; 1.创建一个名为 cesh的数据库 2.在这个数据库内 创建一个名为 xinxi 的表要求该表可以包含&#xff1a;编号&#xff0c;姓名&#xff0c;备注的信息 3.为 ceshi 表 添加数据 4.为xinxi 表的数据设置中文别名 5.查询 在 xinxi 表中编号 为2 的全部…...

【考研数学】线形代数第三章——向量 | 3)向量组秩的性质、向量空间、过渡矩阵

文章目录 引言三、向量组等价、向量组的极大线性无关组与秩3.2 向量组秩的性质 四、 n n n 维向量空间4.1 基本概念4.2 基本性质 写在最后 引言 紧接前文学习完向量组秩的基本概念后&#xff0c;继续往后学习向量的内容。 三、向量组等价、向量组的极大线性无关组与秩 3.2 向…...

【技术】SpringBoot Word 模板替换

SpringBoot Word 模板替换 什么是 Word 模板替换如何实现 Word 模板替换 什么是 Word 模板替换 模板一般是具有固定格式的内容&#xff0c;其中一部分需要替换。Word 模板通俗的讲是以 Word 的形式制作模板&#xff0c;固定格式和内容&#xff0c;然后将其中的一部分数据替换掉…...

java jni nv21和nv12互转

目录 libyuv性能比较 NV12 NV21 YUV420格式介绍 jni YUV420toYUV420SemiPlanar java YUV420toYUV420SemiPlanar java NV12toYUV420SemiPlanar jni NV12toYUV420SemiPlanar...

后端面试话术集锦第二篇:spring boot面试话术

🚗后端面试集锦目录 💖后端面试话术集锦第一篇:spring面试话术💖 💖后端面试话术集锦第二篇:spring boot面试话术💖 💖后端面试话术集锦第三篇:spring cloud面试话术💖 💖后端面试话术集锦第四篇:ElasticSearch面试话术💖 💖后端面试话术集锦第五篇:r…...

Doris中分区和分桶使用教程

1 分区与分桶 Doris中有两层的数据划分&#xff0c;第一层是分区&#xff08;Partition&#xff09;&#xff0c;第二层是分桶&#xff08;Bucket&#xff09;&#xff0c; Partition又能分为Range分区和List分区。 Bucket仅支持Hash方式。 1.1 Partition 只能指定…...

电脑不安装软件,怎么将手机文件传输到电脑?

很多人都知道&#xff0c;AirDroid有网页版&#xff08;web.airdroid.com&#xff09;。 想要文件传输&#xff0c;却不想在电脑安装软件时&#xff0c;AirDroid的网页版其实也可以传输文件。 然而&#xff0c;要将文件从手机传输文件到网页端所在的电脑时&#xff0c;如果按…...

vue3 publish 出现的问题

vue3项目使用 yarn build 编译出dist文件&#xff0c; 发布后出现错误 #问题与解决 1)登录迭代错误(Maximum call stack size exceeded) >deepclone 的问题 在 GrandhallLayout 中判断菜单和权限中; const mainMenu cloneDeep(router.getRoutes()) lodash.clonedee…...

网络防御和入侵检测

网络防御和入侵检测是维护网络安全的关键任务&#xff0c;可以帮助识别和阻止未经授权的访问和恶意行为。以下是一些基本的步骤和方法&#xff0c;用于进行网络防御和入侵检测。 网络防御&#xff1a; 防火墙设置&#xff1a; 部署防火墙来监控和控制网络流量&#xff0c;阻止…...

【科研论文配图绘制】task5 SciencePlots绘图包入门

【科研论文配图绘制】task5 SciencePlots绘图包入门 task5主要学习了SciencePlots拓展包的出图样式&#xff0c;掌握SciencePlots的安装及具体使用。 SciencePlots作为一个专门用于科研论文绘图的第三方拓展工具包&#xff0c;提供了主流英文科技 期刊(如 Nature、Science 和 …...

R语言常用数学函数

目录 1. - * / ^ 2.%/%和%% 3.ceiling,floor,round 4.signif,trunc,zapsamll 5.max,min,mean,pmax,pmin 6.range和sum 7.prod 8.cumsum,cumprod,cummax,cummin 9.sort 10. approx 11.approx fun 12.diff 13.sign 14.var和sd 15.median 16.IQR 17.ave 18.five…...

公网远程访问局域网SQL Server数据库

文章目录 1.前言2.本地安装和设置SQL Server2.1 SQL Server下载2.2 SQL Server本地连接测试2.3 Cpolar内网穿透的下载和安装2.3 Cpolar内网穿透的注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 数据库的重要性相信大家都有所了解&…...

Apache Celeborn 让 Spark 和 Flink 更快更稳更弹性

摘要&#xff1a;本文整理自阿里云/数据湖 Spark 引擎负责人周克勇&#xff08;一锤&#xff09;在 Streaming Lakehouse Meetup 的分享。内容主要分为五个部分&#xff1a; Apache Celeborn 的背景Apache Celeborn——快Apache Celeborn——稳Apache Celeborn——弹Evaluation…...

华为数通方向HCIP-DataCom H12-821题库(单选题:141-160)

第141题 Router-LSA 能够描述不同的链路类型&#xff0c;不属于Router LSA 链路类型的是以下哪一项? A、Link Type 可以用来描述到末梢网络的连接&#xff0c;即 SubNet B、Link Type 可以用来描述到中转网络的连接&#xff0c;即 TranNet C、Link Type 可以用来描述到另一…...

Windows-docker集成SRS服务器的部署和使用

Windows-docker集成SRS服务器的部署和使用 一、Windows Docker安装 Docker Desktop 官方下载地址&#xff1a; https://docs.docker.com/desktop/install/windows-install/ 下载windows版本的就可以了。 注意&#xff1a;此方法仅适用于 Windows 10 操作系统专业版、企业版、…...

element-ui table表格滚动条拉到最右侧 表头与内容不能对齐

1.问题概述 当表格数据太多&#xff0c;会出现纵向滚动条和横向滚动条&#xff0c;把横向滚动条拉到最右侧时&#xff0c;会出现表头与内容不能对齐的现象。 2.解决方法 1.当页面数据加载完毕后&#xff0c;在后面加上 this.$nextTick(() > {this.$refs.table.doLayout()…...

React中的性能测试工具组件Profiler的基本使用

React中的性能测试工具组件Profiler是一个非常有用的工具&#xff0c;它可以帮助我们分析React应用程序的性能瓶颈。在本文中&#xff0c;我们将学习如何使用Profiler组件来测试React应用程序的性能。 首先&#xff0c;让我们来了解一下Profiler组件的基本用法。在React中&…...

提升生产效率,降低运维成本:纺织业物联网网关应用

在众多物联网技术应用中纺织业正逐渐崭露头角。物联网技术通过无线连接纺织设备、PLC、传感器&#xff0c;实现了纺织厂的生产数据信息的远程监控和数据采集、远程管理&#xff0c;为企业提供了更高效、智能的生产方式。智联物联小编在本文中将重点介绍纺织业物联网的应用与通讯…...

【学习笔记】求解线性方程组的G-S迭代法

求解线性方程组的G-S迭代法 // 运行不成功啊function [x,k,index] Gau_Seid(A,b,ep,it_max) % 求解线性方程组的G-S迭代法&#xff0c;其中 % A为方程组的系数矩阵 % b为方程组的右端项 % ep为精度要求&#xff0c;省缺为1e-5 % it_max为最大迭代次数&#xff0c;省缺为100 % …...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...