背景
spring-security
为了升级spring-security到5.6.4及以上,spring-security适配的springboot是2.6及以上。
spring cloud alibaba
方便以后适配spring cloud alibaba的较新版本。
\
Spring Cloud
只升级SpringBoot到2.6.11而不升级SpringCloud会有兼容性问题,报错代码参考Caused by: java.lang.ClassNotFoundException
PageRequest和Sort
PageRequest和Sort有语法上的变更
kafka
幂等性问题
问题:
升级之后无法正常发送消息
异常信息忘记截图了,跟message version有关
原因:
在kafaka的生产幂等性支持在0.11版本中支持https://www.conduktor.io/kafka/idempotent-kafka-producer/
目前我们正式的kafka版本为0.10.2.1,测试环境的版本为1.1.0,也就是说正式环境不支持生产幂等性
kafka-clients 未升级之前的kafka-clients的版本2.6.0(强制指定),升级之后的kafka-clients版本为3.0https://spring.io/projects/spring-kafka#overview
关于3.0的版本变化可以看KIP-639它将幂等性的配置默认变为true,但是我们正式的kafka还不支持生产幂等性
解决方式:
下方的兼容建议也是将幂等性配置关闭 即可
spring.kafka.producer.properties.enable.idempotence=false
Jedis
JedisCommands包路径
未注入IOC的RedisConnectionFactory Bean
之前这里是不用自己调用jedisConnectionFactory.afterPropertiesSet()的,新版本增加了一个成员变量initialized,spring初始化bean的时候将其赋值为true,但是我们这里自己new了一个JedisConnectionFactory的话initialized的默认值为false,如果不调用一次afterPropertiesSet之后会报错:
API
set
RedisScript.of
这里也是api的变化引起的。2.1.12.RELEASE中的RedisScript.of方法:
2.6.6.RELEASE中的RedisScript.of方法:
可以看到方法返回新增了泛型约束,在这里范型为List,List和List不能直接转换,因此报错。
Lettuce
NOAUTH
NOAUTH Authentication required
升级data服务时遇到无法连接redis
其原因是升级后使用的spring-boot-starter-data-redis版本为2.6.11,data服务未指定RedisConnectionFactory,使用其默认的自动装配类RedisAutoConfiguration完成配置而RedisAutoConfiguration配置类默认使用的redis客户端为Lettuce,使用@Import实现
在Lettuce6的仓库中确实有关于NOAUTH Authentication required的issues ,但是官方并没有提供milestone而是强制指定协议为RESP2来解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.chinaroad.parking.config;import io.lettuce.core.ClientOptions;import io.lettuce.core.protocol.ProtocolVersion;import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;@Configuration public class SpringConfig implements LettuceClientConfigurationBuilderCustomizer { @Override public void customize (LettuceClientConfiguration.LettuceClientConfigurationBuilder clientConfigurationBuilder) { clientConfigurationBuilder.clientOptions(ClientOptions.builder() .protocolVersion(ProtocolVersion.RESP2) .build()); } }
Mongo
升级SpringBoot到2.6.11以后,mongodb-driver的版本被升到了4.4.2。
mongo兼容性
Mongodb-driver4.x的驱动是兼容3.x的mongo的,这点没问题(目前我们生产环境的mongo大都是3.x的)。
API
api变化很大,需要修改代码。
MongoDatabaseFactory
isModifiedCountAvailable
变更前
updateResult.isModifiedCountAvailable();
变更后
updateResult.getModifiedCount() > 0
updateResult.isModifiedCountAvailable()这个方法在4.4的mongo-driver中已经没有了,并且从3.12的mongo-driver中就已经标记@Deprecated了,因此这里我直接返回true。
mongo-java-driver/3.12/javadoc
连接配置修改
新版本中需要将mongo连接配置分离,不能全部写入uri中,否则启动会触发校验异常 Invalid mongo configuration, either uri or host/port/credentials/replicaSet must be specified
修改前
spring.data.mongodb.uri=mongodb://xxx:xxx@mongo.aax6.cn:27017/admin
修改后
1 2 3 4 5 spring.data.mongodb.host =mongo.aax6.cn spring.data.mongodb.port =27017 spring.data.mongodb.authentication-database =admin spring.data.mongodb.username =xxx spring.data.mongodb.password =xxx
_id扁平化处理
官方issues再spring-data-mongo2.0.x中document在反序列化为entity时会经过org.springframework.data.mongodb.core.MongoTemplate.UnwrapAndReadDocumentCallback处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Override public T doWith (@Nullable Document object) { if (object == null ) { return null ; } Object idField = object.get(Fields.UNDERSCORE_ID); if (!(idField instanceof Document)) { return super .doWith(object); } Document toMap = new Document(); Document nested = (Document) idField; toMap.putAll(nested); for (String key : object.keySet()) { if (!Fields.UNDERSCORE_ID.equals(key)) { toMap.put(key, object.get(key)); } } return super .doWith(toMap); }
其核心是将_id字段扁平化处理,将字段层级向上提升。但是在spring-data-mongo3.0.x中被取消,导致一些mongo的aggregation查询反序列化失败。解决方法 创建监听器监听在反序列化之前发送的事件org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent#AfterLoadEvent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class UnwrapAndReadDocumentListener extends AbstractMongoEventListener <Object > { @Override public void onAfterLoad (AfterLoadEvent<Object> event) { Document document; if (event != null && (document = event.getDocument()) != null ) { Object idField = document.get(Fields.UNDERSCORE_ID); if (idField instanceof Document) { Document nested = (Document) idField; document.putAll(nested); } } super .onAfterLoad(event); } }
在监听器中再将文档_id扁平化处理即可
count()、countDocuments()、estimatedDocumentCount
3.8驱动 的文档描述
4.4驱动 的文档描述
Spring
spring循环依赖
2.6开始新增配置,是否允许循环依赖,默认不允许。导致代码中存在大量循环依赖的异常抛出解决方案增加配置
1 spring.main.allow-circular-references =true
Lombok
升级SpringBoot到2.6.11之后, spring-boot-dependencies中约束了lombok的版本为1.18.24,而之前的版本是1.18.12。1.18.24版本的Lombok与mapstruct存在兼容性问题。参考Can I use MapStruct together with Project Lombok? 1.18.16以上的Lombok要想与MapStruct一起使用需要额外引入lombok-mapstruct-binding依赖。
1 2 3 4 5 6 <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok-mapstruct-binding --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> </dependency>
这里我选择在parent中指定lombok版本。
eureka
服务列表
eureka-client与eureka-server版本不一致,导致client无法从server中拉取服务列表
解决方案:
1,升级eureka-extension.jar 为2.0.x 版本
eureka-extension.jar的2.0.x版本中添加配置类RibbonEurekaClientConfig — 自定义bean获取server中的服务列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class RibbonEurekaClientConfig { @Bean public IPing ribbonPing (IClientConfig config) { return new DummyPing(); } @Bean public IRule ribbonRule (IClientConfig config) { return new AvailabilityFilteringRule(); } @Autowired DiscoveryClient discoveryClient; @Bean public ServerList<Server> getServerList (IClientConfig config) { return new ServerList<Server>() { @Override public List<Server> getInitialListOfServers () { return new ArrayList<>(); } @Override public List<Server> getUpdatedListOfServers () { List<Server> serverList = new ArrayList<>(); List<ServiceInstance> list = discoveryClient.getInstances(config.getClientName()); for (ServiceInstance instance : list) { serverList.add(new Server(instance.getHost(), instance.getPort())); } return serverList; } }; } }
2,启动类中指定获取服务的bean
1 2 @RibbonClients(defaultConfiguration = RibbonEurekaClientConfig.class)
configserver: property to your configuration
官网参考:https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#config-data-import
异常信息
1 2 3 4 5 6 7 8 9 10 Description: No spring.config.import property has been defined Action: Add a spring.config.import=configserver: property to your configuration. If configuration is not required add spring.config.import=optional:configserver: instead. To disable this check, set spring.cloud.config.enabled=false or spring.cloud.config.import-check.enabled=false.
Spring Boot2.4引入了一种通过spring.config.import属性导入配置数据的新方法。这现在是绑定到配置服务器的默认方式。技术上bootstrap.properties和bootstrap.yml 文件已弃用 。
解决方案1
在文件中添加以下依赖pom.xml项:
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-bootstrap</artifactId > </dependency >
解决方案2:
将所有属性从boostrap.properties到移动application.properties(也可以.yml)
删除bootstrap.properties文件
替换spring.cloud.config.uri=http://localhost:8888 为 spring.config.import=configserver:http://localhost:8888
这是告诉您 Spring Boot 应用程序要从运行的 Spring Cloud Config 服务加载属性localhost:8888的正确方法。
swagger
spring-boot2.6.x+与swagger3.0.0-不兼容
注释掉所有swagger相关注解,等下一步推行新的接口文档方案后整体迁移 异常提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper' ; nested exception is java.lang.NullPointerException at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181 ) at org.springframework.context.support.DefaultLifecycleProcessor.access$200 (DefaultLifecycleProcessor.java:54 ) at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356 ) at java.lang.Iterable.forEach(Iterable.java:75 ) at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155 ) at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123 ) at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935 ) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586 ) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145 ) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:745 ) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:420 ) at org.springframework.boot.SpringApplication.run(SpringApplication.java:307 ) at so.sao.ucode.data.DataApplication.main(DataApplication.java:44 ) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 ) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 ) at java.lang.reflect.Method.invoke(Method.java:498 ) at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49 ) Caused by: java.lang.NullPointerException: null at springfox.documentation.spi.service.contexts.Orderings$8. compare(Orderings.java:112 ) at springfox.documentation.spi.service.contexts.Orderings$8. compare(Orderings.java:109 ) at com.google.common.collect.ComparatorOrdering.compare(ComparatorOrdering.java:38 ) at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355 ) at java.util.TimSort.sort(TimSort.java:234 ) at java.util.Arrays.sort(Arrays.java:1438 ) at com.google.common.collect.Ordering.sortedCopy(Ordering.java:816 ) at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers(WebMvcRequestHandlerProvider.java:57 ) at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper$2. apply(DocumentationPluginsBootstrapper.java:138 ) at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper$2. apply(DocumentationPluginsBootstrapper.java:135 ) at com.google.common.collect.Iterators$8. transform(Iterators.java:794 ) at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:48 ) at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:48 ) at com.google.common.collect.Iterators$5. hasNext(Iterators.java:543 ) at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:268 ) at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:226 ) at com.google.common.collect.FluentIterable.toList(FluentIterable.java:334 ) at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.defaultContextBuilder(DocumentationPluginsBootstrapper.java:111 ) at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.buildContext(DocumentationPluginsBootstrapper.java:96 ) at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:167 ) at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178 ) ... 17 common frames omitted
问题分析
Springfox 使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher。解决方案一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Bean public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping (WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) { List<ExposableEndpoint<?>> allEndpoints = new ArrayList(); Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints(); allEndpoints.addAll(webEndpoints); allEndpoints.addAll(servletEndpointsSupplier.getEndpoints()); allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); String basePath = webEndpointProperties.getBasePath(); EndpointMapping endpointMapping = new EndpointMapping(basePath); boolean shouldRegisterLinksMapping = this .shouldRegisterLinksMapping(webEndpointProperties, environment, basePath); return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null ); } private boolean shouldRegisterLinksMapping (WebEndpointProperties webEndpointProperties, Environment environment, String basePath) { return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT)); }
maven
maven打包插件版本不兼容问题
dockers启动镜像异常
no main manifest attribute, in app.jar
异常原因: 插件spring-boot-maven-plugin版本过高
解决方案
pom文件中规定maven-plugin版本
1 2 3 4 5 6 7 8 9 10 11 12 <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <version > 2.1.0.RELEASE</version > <executions > <execution > <goals > <goal > repackage</goal > </goals > </execution > </executions > </plugin >
RabbitMQ
Rabbit health check failed
springcloud stream 报错 Rabbit health check failed
原因
就是因为使用了spring.cloud.stream.binders.*.environment属性配置rabbitMQ的相关信息,但是没配置spring.rabbitmq。这就导致自动配置检测到类路径下有rabbit相关的类,就配置了rabbit相关的Bean。其中org.springframework.boot.actuate.amqp.RabbitHealthIndicator负责监控rabbit的连接状况,通过下面这个配置类自动配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package org.springframework.boot.actuate.autoconfigure.amqp;import java.util.Map;import org.springframework.amqp.rabbit.core.RabbitTemplate;import org.springframework.boot.actuate.amqp.RabbitHealthIndicator;import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;import org.springframework.boot.actuate.health.HealthContributor;import org.springframework.boot.autoconfigure.AutoConfigureAfter;import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration(proxyBeanMethods = false) @ConditionalOnClass(RabbitTemplate.class) @ConditionalOnBean(RabbitTemplate.class) @ConditionalOnEnabledHealthIndicator("rabbit") @AutoConfigureAfter(RabbitAutoConfiguration.class) public class RabbitHealthContributorAutoConfiguration extends CompositeHealthContributorConfiguration <RabbitHealthIndicator , RabbitTemplate > { @Bean @ConditionalOnMissingBean(name = { "rabbitHealthIndicator", "rabbitHealthContributor" }) public HealthContributor rabbitHealthContributor (Map<String, RabbitTemplate> rabbitTemplates) { return createContributor(rabbitTemplates); } }
但是没有检测到spring.rabbitmq相关的配置,就使用了默认的配置,尝试连接localhost:5672,然后就出现了org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect。问题的关键在于,spring.cloud.stream.binders..environment为每个binder创建了单独的上下文环境,跟application context是完全隔离的。所以,仅仅配置了spring.cloud.stream.binders. .environment,使Actuator从application context中没有找到rabbitmq的配置。
解决方案1:(不推荐)
禁用RabbitHealthIndicator。上面提到的配置类上还有一个@ConditionalOnEnabledHealthIndicator(“rabbit”),意思就是:配置management.health.rabbit.enabled为true的时候生效。禁用即可解决。
management.health.rabbit.enable=false
解决方案2:(推荐)
配置spring.rabbitmq用于actuator的健康检测
1 2 3 4 5 6 spring: rabbitmq: host: 10.0 .40 .8 virtual-host: /test username: guest password: guest
Junit4迁移指南
https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4
MapStruct和Lombok
doc: https://mapstruct.org/documentation/stable/reference/html/#lombok推荐配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <properties > <org.mapstruct.version > 1.5.3.Final</org.mapstruct.version > <org.projectlombok.version > 1.18.16</org.projectlombok.version > <maven.compiler.source > 1.8</maven.compiler.source > <maven.compiler.target > 1.8</maven.compiler.target > </properties > <dependencies > <dependency > <groupId > org.mapstruct</groupId > <artifactId > mapstruct</artifactId > <version > ${org.mapstruct.version}</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${org.projectlombok.version}</version > <scope > provided</scope > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.8.1</version > <configuration > <source > 1.8</source > <target > 1.8</target > <annotationProcessorPaths > <path > <groupId > org.mapstruct</groupId > <artifactId > mapstruct-processor</artifactId > <version > ${org.mapstruct.version}</version > </path > <path > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${org.projectlombok.version}</version > </path > <path > <groupId > org.projectlombok</groupId > <artifactId > lombok-mapstruct-binding</artifactId > <version > 0.2.0</version > </path > </annotationProcessorPaths > </configuration > </plugin > </plugins > </build >
spring-boot-devtools
异常问题
loader constraint violation: loader (instance of sun/misc/Launcher$AppClassLoader) previously initiated loading for a different type with name “so/sao/ucode/enums/common/Deleted”
原因:
devtools自定义了类加载器,打破了双亲委派机制,导致类加载冲突启动失败devtools的工作原理
spring boot使用了两个类加载器(classloader),对于不会变化的类,比如引入的第三方jar包中的类,加载到一个base classloader中,而开发者实际编写的类,被加载到另一个restart classloader中。当应用重启的时候,整个restart classloader被销毁后重建,而base classloader则保留下来不必再次加载。通过这种方式实现restart效率的提升,模拟了这种方式下的"热部署"。
解决方案
把pom文件中devtools的依赖注释掉。
restTemplate
内部服务调用负载均衡失败
异常问题http://data/test这样的url无法通过restTemplate解析并调用。
原因
可以解析并调用的原理是,将所有添加了@Loadbalanced的RestTemplate添加到拦截器中,在拦截器中解析url,做到一个通过服务名负载均衡到不同的ip。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Configuration(proxyBeanMethods = false) @Conditional(RetryMissingOrDisabledCondition.class) static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor loadBalancerInterceptor (LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer (final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } }
升级后拦截器的生效条件中新增了,需要配置spring.cloud.loadbalancer.retry.enabled = false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static class RetryMissingOrDisabledCondition extends AnyNestedCondition { RetryMissingOrDisabledCondition() { super (ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class RetryTemplateMissing { } @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "false") static class RetryDisabled { } }
spring.cloud.loadbalancer.retry.enabled默认为true,也就是默认不支持restTemplate的负载均衡。
解决方案1
增加配置
spring.cloud.loadbalancer.retry.enabled=false
默认重试机制为get请求,重试其他实例1次,因此禁用后影响极其微小。
解决方案二:
改写为feign接口
zip4j 升级
原setPassword函数没了,密码在构造ZipFile的时候直接传
ZipFile 目录变更
一些配置的变化
支持 zip 格式规范指定的 unicode 文件名 (UTF-8)。Zip4j 在创建 zip 文件时将使用 UTF-8 文件名和文件注释编码。提取 zip 文件时,Zip4j 将仅使用 UTF-8 编码,前提是根据 zip 文件格式规范设置了适当的标头标志。如果未设置此标志,Zip4j 将使用仅支持扩展 ASCII 字符的 CP437 编码。
spring配置项过期
spring.mvc.locale配置过期
替换为spring.web.locale
解决方案
在core.jar中增加依赖检查配置更新过期, 已防止配置过期产生其他位置问题
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-properties-migrator</artifactId > </dependency >
Jackson
序列化java.time下的类型额外处理
异常
1 Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: java.util.ArrayList[0]->org.springframework.util.LinkedCaseInsensitiveMap["jobExecTime"])
**原因:**com.fasterxml.jackson.databind.util.BeanUtil#checkUnsupportedType
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static String checkUnsupportedType (JavaType type) { final String className = type.getRawClass().getName(); String typeName, moduleName; if (isJava8TimeClass(className)) { if (className.indexOf('.' , 10 ) >= 0 ) { return null ; } typeName = "Java 8 date/time" ; moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" ; } else if (isJodaTimeClass(className)) { typeName = "Joda date/time" ; moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-joda" ; } else { return null ; } return String.format("%s type %s not supported by default: add Module \"%s\" to enable handling" , typeName, ClassUtil.getTypeDescription(type), moduleName); } private static boolean isJava8TimeClass (String className) { return className.startsWith("java.time." ); }
从源码可以看出,jackson无法解析java.time包下的数据类型。LocalDateTime的全路径为java.time.LocalDateTime,被限制序列化。
下图为jdbc版本升级前后(8.0.21–>8.0.30)查出的字段类型比较(左图升级后,右图升级前)
jdbc升级导致字段映射类型变化原因
源码对比
8.0.21com.mysql.cj.jdbc.result.ResultSetImpl#getObject(int)
1 2 case DATETIME: return this.getTimestamp(columnIndex);
8.0.30com.mysql.cj.jdbc.result.ResultSetImpl#getObject(int)
1 2 case DATETIME: return getLocalDateTime(columnIndex);
解决方案
引入依赖
1 2 3 4 5 6 <!--解决Jackson时间转换错误--> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <!-- <version>${jackson-datatype-jsr310.version}</version>--> </dependency>
objectMapper设置配置
objectMapper.registerModule(new JavaTimeModule());