从springboot 3.2升级到springboot 4.0.1的坑
Table of Contents
这几天将应用依赖的springboot从3.2升级到4.0.1,同时尝试了maven4.0.0rc5,遇到了一些坑,记录一下。
一些包路径和方法签名的修改
- springboot configuration相关的包,命名方式都进行了修改。如果在自定义的configuration中引用了springboot提供的 configuration类(例如使用了import来引用其他的配置类,或者使用了springboot内置properties)需要修改import的类。
- @MockBean注解修改,新版本的spring test中将mockito集成的MockBean改成了
org.springframework.test.context.bean.override.mockito.MockitoBean。 org.springframework.web.util.UriComponentsBuilder中,之前经常使用的fromHttpUrl方法没有了,需要修改成fromUriString。
这些路径修改比较容易,编译的时候有报错就可以发现了。
一些和配置相关的,就需要启动之后才能发现,例如:
- jpa的
spring.jpa.database-platform的配置,老版本使用的是org.hibernate.dialect.MySQL8Dialect,现在需要修改成org.hibernate.dialect.MySQLDialect。因为本地启动使用了H2,这个到测试环境启动才发现。
行为的修改
- H2数据库版本升级,默认会将数据库字段变成大写。由于我们的应用中混合使用了jpa和r2dbc,会导致本地调试的时候,r2dbc无法识别到jpa自动创建的数据库表,
或者是字段找不到。即使设置了
MODE=MySQL,也需要给H2数据库强制加上:- DATABASE_TO_UPPER=FALSE
- CASE_INSENSITIVE_IDENTIFIERS=TRUE 当然了,这个后续我会强制把所有字段都使用spring-data的注解进行显式标注,避免以后要迁移数据库等出现问题。
- 如果使用了
org.mockito.Mock注解来生成mock bean,现在需要在测试代码执行之前显式调用MockitoAnnotations.openMocks, 否则不会主动初始化并变成spring bean。
最大的坑——netty
这次升级springboot 4.0.1到目前为止遇到最大的坑是netty,因为springboot 4.0.1默认使用的是netty 4.2,应用启动的时候没有报错, 但是发现netty无法解析k8s内部域名。当时因为升级springboot的同时,还升级了java版本,将java 17升级到了java 21, 以前听说过基础镜像使用基于alpine的时候可能会存在dns解析有问题的情况。
因此第一次尝试更换基础镜像,但是不管是使用基于ubi的镜像,还是基于ubuntu的镜像,都无法解决netty无法解析k8s内部域名的问题。
通过对详细异常栈的定位,发现netty在Linux系统下,默认使用epoll来实现监听socket,在初始化socket的时候,
会通过setsockopt()这个native方法来设置socket的属性。这个操作内核直接返回了Protocol not available错误,
但是没有更详细的错误表明到底设置的是什么东西。参照AI给出的命令通过strace也没有追踪到setsockopt()的系统调用
(可能是因为netty在初始化的时候就在尝试创建socket,而我的镜像太简单了,Java进程直接作为pid为1的进程运行,没法提前开始拦截系统调用。)
由于这个异常在我本地启动的时候没有复现,一度想尝试不使用native的epoll,而是让netty使用java nio来实现了。
还好最后在netty的issue(https://github.com/netty/netty/issues/16082)
中找到了原因。Netty在io.netty.channel.epoll.LinuxSocket.setIpMulticastAll这个native方法会优先设置IPv6的设置(https://github.com/netty/netty/blob/4.2/transport-native-epoll/src/main/c/netty_epoll_linuxsocket.c#L253),但是我们使用的k8s集群只开启了IPv4,因此在设置IPv6的选项的时候就会报错。
最终的解决方案是在应用的环境变量中设置了-Djava.net.preferIPv4Stack=true,强制JVM优先使用IPv4。