SpringBoot项目升级到3.*,并由JDK8升级到JDK21

news/2024/11/21 12:01:18/

文章目录

  • 技术选型说明
  • JDK21的Demo项目下载
  • 升级过程出现的问题及解决
    • 1、程序包javax.servlet.http不存在
      • 1.1、java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
      • 1.2、javax.validation包替换为jakarta.validation
      • 1.3、jakarta的名字由来
    • 2、mybatis-plus升级
    • 3、mybatis-plus多数据源支持
    • 4、redis配置调整
    • 5、openfeign配置调整到spring.cloud下
    • 6、升级到swagger3
      • 6.1、swagger的新注解
      • 6.2、java.lang.NoSuchMethodError: 'boolean org.apache.commons.lang3.math.NumberUtils.isCreatable(java.lang.String)'
    • 7、No SLF4J providers were found.
  • 其它问题
    • import java.util.concurrent.TimeUnit 报错:
    • FeignClient 加 GetMapping,实际自动转POST发出
  • 单元测试
    • 步骤
    • 错误处理
      • java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test
      • 提示:Java.lang.Exception: No runnable methods
      • 测试类不支持注解:RequiredArgsConstructor
      • Caused by: org.springframework.cloud.commons.ConfigDataMissingEnvironmentPostProcessor$ImportException: No spring.config.import set
      • 启动测试时,Autowired注解的类为null

技术选型说明

前几个月搞新项目,做技术选型时,评估了一下,决定使用JDK21,主要的评估点:

  • JDK21已经出了LTS长期支持版本,而且按Oracle官方说明,是免费使用的:https://www.oracle.com/hk/java/technologies/downloads/#java21
    JDK 21 binaries are free to use in production and free to redistribute, at no cost, under the Oracle No-Fee Terms and Conditions (NFTC).
  • 从JDK8到JDK21,引入了很多的性能优化,包括GC改进,之前看到过一个性能评测,同样的代码,在JDK21也比JDK8下运行要快10%~30%,不过现在找不到那个链接了,不过google搜索一下还是有很多类似的性能评测文章的;
  • SpringBoot的3.*最新版本,已经不支持JDK8了,例如现在的Stable稳定版3.3.5,要求JDK17:https://docs.spring.io/spring-boot/system-requirements.html
    而SpringBoot2.*的商业支持只到2025年2月:https://spring.io/blog/2022/05/24/preparing-for-spring-boot-3-0
  • 拥有经常被别人安利的虚拟线程(我还没用过)
  • 新项目,没有任何历史债务,又是探索型项目,工期要求不那么急,那就让团队进步一下,搞吧。

最终决定选型:JDK21 + SpringBoot3.3.1
注:JDK21,有很多公司都推出了发行版,基本上都可以下载和使用,这里列举几个:

  • oralce推出的NFTC版本:https://www.oracle.com/hk/java/technologies/downloads/#java21
    NFTC是指:Oracle No-Fee Terms and Conditions许可
  • 微软LTS发行版:https://learn.microsoft.com/zh-cn/java/openjdk/download-major-urls#openjdk-21-lts
  • Eclipse发行版:https://adoptium.net/zh-CN/temurin/releases/
  • OpenLogic发行版:https://www.openlogic.com/openjdk-downloads

我在生产环境用的当然还是Oracle的版本了。

JDK21的Demo项目下载

为方便后续问题解决,或快速创建新的JDK21项目,
写了一个基于JDK21+ spring-boot-starter3.3.1 + spring-cloud-starter-openfeign4.1.2的项目,放在github上,
有兴趣可以下载:
https://github.com/youbl/study/tree/master/jdk21-demo

升级过程出现的问题及解决

1、程序包javax.servlet.http不存在

在Controller里,一般会使用HttpServletRequestHttpServletResponse
在JDK8配套的SpringBoot2.*里,依赖的引用是:import javax.servlet.http.HttpServletRequest
而在SpringBoot3依赖的引用是import jakarta.servlet.http.HttpServletRequest
其它javax.servlet.http依赖都同样调整即可,注意要确认pom.xml添加了如下依赖:

<dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>5.0.0</version>
</dependency>

注:代码中涉及javax的package,都要对应替换,下面举2个例子:

1.1、java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter

如果代码报这个错,也是需要修改依赖,从javax 改为 jakarta,在pom.xml里引入:

<dependency><groupId>jakarta.xml.bind</groupId><artifactId>jakarta.xml.bind-api</artifactId><version>3.0.1</version>
</dependency>

但是,我在对接阿里云时,它们的SDK报这个错,那这个就没法改pom了,因为它们的SDK代码里写死了javax,此时只能把javax的依赖加回来了,在pom.xml里,添加:

<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.0</version>
</dependency>
<dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>2.3.0</version>
</dependency>
<dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-core</artifactId><version>2.3.0</version>
</dependency>
<dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version>
</dependency>

1.2、javax.validation包替换为jakarta.validation

同样如果使用了spring的validation,对应的依赖也要换成jakarta:

<dependency><groupId>jakarta.validation</groupId><artifactId>jakarta.validation-api</artifactId><version>3.0.2</version>
</dependency>

注:如果项目添加了下面的swagger依赖,那边自动集成了,就可以不需要自己加了。

1.3、jakarta的名字由来

当时觉得jakarta这个命名很山寨,特意查了一下由来,发现是我自己山寨了,参考维基百科:https://zh.wikipedia.org/wiki/Jakarta%E9%A1%B9%E7%9B%AE

Jakarta的名称与印度尼西亚的首都雅加达(Jakarta)并无直接关系,
实际上它是根据Sun Microsystems公司当时讨论创建这个项目时的会议室命名的。

另外,jakarta.ee官网也解释了这个命名的由来:https://jakarta.ee/about/faq/
那里也是引用了维基百科的会议室来源说法,并在2018年2月进行了投票,64%的人支持Jakarta EE这个命名。
而维基百科的Java_EE的页面没有找到相关说明:https://zh.wikipedia.org/wiki/Jakarta_EE

那为什么要改名呢?依据一些未经考证的说明,是因为Oracle把JavaEE移交给Eclipse基金会,同时不允许Eclipse基金会继续使用Java名号,所以才发起改名投票。

2、mybatis-plus升级

参考:https://github.com/baomidou/mybatis-plus
SpringBoot2用这个:

<!-- 这是SpringBoot2的 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version>
</dependency>

而升级到SpringBoot3,要用这个artifactId:mybatis-plus-spring-boot3-starter

<!-- 这是SpringBoot3的 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.5</version>
</dependency>
<!-- 搭配的mysql驱动 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version>
</dependency>

3、mybatis-plus多数据源支持

参考:https://github.com/baomidou/dynamic-datasource
SpringBoot3要使用如下依赖:

<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot3-starter</artifactId><version>4.3.1</version>
</dependency>

4、redis配置调整

在SpringBoot2里,redis的yml配置写法如下:

spring:redis:host: localhostport: 6379database: 15password: 123456

升级到SpringBoot3后,redis的yml配置写法如下:

spring:data:redis:host: localhostport: 6379database: 15password: 123456

5、openfeign配置调整到spring.cloud下

在之前,feign的相关配置是这样的:

feign:client:config:default:logger-level: full

升级到SpringBoot3(对应spring-cloud-starter-openfeign4.1.2以上)后,相关的配置迁移了,参考: https://docs.spring.io/spring-cloud-openfeign/reference/spring-cloud-openfeign.html
新的配置是这样的:

spring:cloud:openfeign:client:config:default:logger-level: full

我在另一篇文章也做了代码断点调试来说明代码调用位置,参考:https://youbl.blog.csdn.net/article/details/109047987

6、升级到swagger3

在SpringBoot3项目的pom.xml里添加依赖,即可,启动项目使用地址:http://localhost:8080/swagger-ui.html
参考官网说明:https://springdoc.org/

<dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.0.2</version>
</dependency>

如果想修改swagger api页面的介绍,可以在代码里定义如下Bean:

@Configuration
public class SwaggerApiConfig {// 参考官网:https://springdoc.org/@Beanpublic OpenAPI springOpenAPI() {Info info = new Info().title("beinet.cn jdk21 API demo文档").description("这是水边提供的jdk21代码demo,参考:https://youbl.blog.csdn.net/").version("0.0.1") // 版本号.license(new License().name("Apache 2.0").url("https://youbl.blog.csdn.net/"));ExternalDocumentation doc = new ExternalDocumentation().description("水边的Blog文档").url("https://youbl.blog.csdn.net/");return new OpenAPI().info(info).externalDocs(doc);}
}

注意:
如果项目报如下错误:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'swaggerWebMvcConfigurer' defined in class path resource [org/springdoc/webmvc/ui/SwaggerConfig.class]: Unsatisfied dependency expressed through method 'swaggerWebMvcConfigurer' parameter 0: Error creating bean with name 'org.springdoc.core.properties.SwaggerUiConfigParameters': Failed to instantiate [org.springdoc.core.properties.SwaggerUiConfigParameters]: Constructor threw exception

这是因为内置的commons-lang3版本有问题,需要自定义版本,pom.xml参考:

<dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.0.2</version><exclusions><exclusion><artifactId>commons-lang3</artifactId><groupId>org.apache.commons</groupId></exclusion></exclusions>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.14.0</version>
</dependency>

6.1、swagger的新注解

相对于原来的swagger2,新的swagger3的注解全部换掉了,按官网说明,新旧注解对应关系如下:https://springdoc.org/#migrating-from-springfox

@Api → @Tag
@ApiIgnore → @Parameter(hidden = true) or @Operation(hidden = true) or @Hidden
@ApiImplicitParam → @Parameter
@ApiImplicitParams → @Parameters
@ApiModel → @Schema
@ApiModelProperty(allowEmptyValue = true) → @Schema(nullable = true)
@ApiModelProperty → @Schema
@ApiOperation(value = "foo", notes = "bar") → @Operation(summary = "foo", description = "bar")
@ApiParam → @Parameter
@ApiResponse(code = 404, message = "foo") → @ApiResponse(responseCode = "404", description = "foo")

但是实际我在应用中,发现也没有非常明确的对应关系,我的作法:

  • 一般在Dto上,统一使用@Schema注解,如:
@Data
@Accessors(chain = true)
@Schema(description = "用户数据")
public class UsersDto {@Schema(description = "用户id,主键")private Long id;@Size(max = 255)@Schema(description = "用户名称")private String name;
  • 在Controller类上,使用@Tag注解,类里的Mapping接口上,使用@Operation注解,如:
@RestController
@RequiredArgsConstructor
@Tag(name = "users", description = "用户增删改查接口类")
public class UsersController {private final UsersService service;@PostMapping("/users/all")@Operation(summary = "用户列表", description = "用户列表查询接口")public ResponseData<List<UsersDto>> findAll(@RequestBody UsersDto dto) {return ResponseData.ok(service.search(dto));}

效果如图,在页面上可以点击“Try it out”进行接口测试,类似于PostMan或Fiddler:
在这里插入图片描述

6.2、java.lang.NoSuchMethodError: ‘boolean org.apache.commons.lang3.math.NumberUtils.isCreatable(java.lang.String)’

如果swagger报如下错误:

java.lang.NoSuchMethodError: 'boolean org.apache.commons.lang3.math.NumberUtils.isCreatable(java.lang.String)'at io.swagger.v3.core.jackson.ModelResolver.resolveMinimum(ModelResolver.java:1831) ~[swagger-core-jakarta-2.2.7.jar:2.2.7]at io.swagger.v3.core.jackson.ModelResolver.resolveSchemaMembers(ModelResolver.java:2222) ~[swagger-core-jakarta-2.2.7.jar:2.2.7]at io.swagger.v3.core.jackson.ModelResolver.resolveSchemaMembers(ModelResolver.java:2177) ~[swagger-core-jakarta-2.2.7.jar:2.2.7]at io.swagger.v3.core.jackson.ModelResolver.resolve(ModelResolver.java:341) ~[swagger-core-jakarta-2.2.7.jar:2.2.7]

那么还是前面说的commons-lang3版本太低问题导致的,要按前面说的自定义升级方式指定高版本。
注:在实际项目中,出过这样一个问题:

  • 在子模块的<dependencies>添加并指定了3.14.0的版本;
  • 在父模块使用<dependencyManagement>指定了3.4.0的版本;
  • 最终构建的结果会使用3.4.0,导致启动报错,当时查了挺久才发现问题。

7、No SLF4J providers were found.

启动报错:

SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.

通常是因为没有添加logging实现依赖,添加 spring-boot-starter-logging 引用解决:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId>
</dependency>

如果你的项目添加了spring-boot-starter-test,那应该不会报这个错。
注:也可以添加其它的依赖实现,格式会有点不好看就是了,参考:https://stackoverflow.com/questions/54652836/found-slf4j-api-dependency-but-no-providers-were-found

其它问题

import java.util.concurrent.TimeUnit 报错:

在idea中会标红,但是不影响使用,升级idea版本可以解决
参考:https://stackoverflow.com/questions/77551293/intellij-idea-jdk-21-issue-with-java-util-concurrent-package-timeunit-class

FeignClient 加 GetMapping,实际自动转POST发出

下面的feign定义,会自动转换为POST发请求,导出报错,路径不存在:

@GetMapping("/users")
ResponseData<UsersDto> pageIdentity(UsersDto dto);

因为默认情况下,feign会把复杂对象作为body进行提交,而http协议规范是不支持GET加body的,因此feign就自动转换为POST了。
解决办法,就是FeignClient不改,让调用的目标接口那边,改用PostMapping来接收body。
如果不改目标接口,在Feign的参数前加 @RequestParam 不能解决问题。

单元测试

步骤

以一个utils的工具类库demo项目为例,添加单元测试步骤:

1、test单元测试代码的目标结构:
在这里插入图片描述

  • 对utils项目的pom.xml,添加依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-test</artifactId><scope>test</scope>
</dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.10.3</version><scope>test</scope>
</dependency>
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope>
</dependency>
  • 在src目录2. 新建子目录: test/java
    注:test目录跟main目录是同级的
  • 在test下新建子目录:resources,并新建文件:application.yml,内容参考:
spring:application:name: beinet-utilsprofiles:active: local
  • 在test/java下新建package,必须跟main/java下的主java文件是相同package
  • 在该新建的package下,新建UtilsTestApplication 文件,内容参考:
    注:因为spring-boot的单元测试要求要有@SpringBootApplication定义的主类存在,而utils之类的项目一般没有
@SpringBootApplication(scanBasePackages = "cn.beinet")
public class UtilsTestApplication {public static void main(String[] args) {SpringApplication.run(UtilsTestApplication.class, args);}
}
  • 在该新建的package下,新建package为testHelper,再在其下新建单元测试类IpHelperTest.java 文件,内容参考:
package cn.beinet.core.utils.testHelper;import cn.beinet.core.utils.UtilsTestApplication;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest(classes = UtilsTestApplication.class)
public class IpHelperTest {@Testpublic void testDemo() {var ts = System.currentTimeMillis();Assert.assertTrue(ts > 1);}
}
  • OK,可以点击 testDemo左边的绿色小三角形,启动测试看看效果。

错误处理

java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=…) with your test

因为@SpringBootTest注解,默认会在当前package下查找主类(即有@SpringBootApplication注解的类)
找不到就会报错,要求在@SpringBootTest注解里指定主类的位置,如:
@SpringBootTest(classes = UtilsTestApplication.class)
可以在test/java下的package下新建一个主类,参考上面步骤

提示:Java.lang.Exception: No runnable methods

这是因为@Test 注解用错了,
正确的Test注解全路径是 org.junit.Test
如果import导入了错误的package,用了 org.junit.jupiter.api.Test 就会报这个错误

测试类不支持注解:RequiredArgsConstructor

如果用了这个注解,会报错:

org.junit.runners.model.InvalidTestClassError: Invalid test class 'xxx.HandleFactoryTest':1. Test class should have exactly one public zero-argument constructor

需要使用Bean时,在测试代码里,直接用 @Autowired 注解即可

Caused by: org.springframework.cloud.commons.ConfigDataMissingEnvironmentPostProcessor$ImportException: No spring.config.import set

这是因为项目依赖了配置中心,而yml中未指定配置中心的配置,需要在yml里指定一下,如:

spring:config:import: configserver:https://config-dev.beinet.cn

注意:其它必要的配置也不能遗漏,比如 spring.application.name

启动测试时,Autowired注解的类为null

如果单元测试依赖spring的Bean,则在该测试类上,必须添加注解:
@RunWith(SpringRunner.class)
如果不依赖Bean,则可以不需要该注解,以加快单元测试执行速度


http://www.ppmy.cn/news/1548746.html

相关文章

根据条件 控制layui的table的toolbar的按钮 显示和不显示

部分代码&#xff1a; <!-----查询条件-----> <input type"date" id"StartDate" onchange"PageList()" /> <input type"date" id"EndDate" onchange"PageList()" /><!-----表格Table-----&…

Python实现随机分布式延迟PSO优化算法(RODDPSO)优化CNN回归模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后关注获取。 1.项目背景 近年来&#xff0c;深度学习技术在计算机视觉、语音识别、自然语言处理等领域取得了显著的成功。卷…

C# 数据结构之【栈】C#栈

1. 描述 栈 &#xff1a;栈遵循后进先出&#xff08;LIFO&#xff09;原则&#xff0c;只能在一端进行插入和删除操作。 2. 应用示例 using System;namespace DataStructure {class Program{static async Task Main(string[] args){// 创建一个栈Stack<int> stack ne…

python常用语法笔记(持续更新)

文章目录 一、基础语法1、sleep休眠2、os系统操作&#xff08;1&#xff09;获取环境变量&#xff08;2&#xff09;os.path操作 3、文件操作&#xff08;1&#xff09;文件读取模式详解&#xff08;2&#xff09;逐行读取文件&#xff08;3&#xff09;逐行写入文件 4、字符串…

CentOS 7 防火墙开启 ,没有开22端口,为什么没有被限制

firewall-cmd --zonepublic --list-all这个命令会显示 public 区域的所有配置&#xff0c;包括允许的服务、端口、源地址等。 假设你执行了上述命令&#xff0c;得到了以下输出&#xff1a; public (active)target: defaulticmp-block-inversion: nointerfaces: eth0sources: …

使用 SMB 协议从win10电脑访问同网段ubuntu电脑文件

​​​​​​1.在 Ubuntu 上设置共享文件夹 在终端中运行以下命令安装 Samba&#xff1a; sudo apt update sudo apt install samba 编辑 Samba 配置文件&#xff1a; sudo nano /etc/samba/smb.conf 在文件末尾添加以下内容&#xff08;假设要共享 用户dy下的Downloads目录&…

使用Cursor和Claude AI打造你的第一个App

大家好&#xff0c;使用Cursor和Claude AI打造应用程序是一个结合智能代码辅助和人工智能对话的创新过程。Cursor是一个编程辅助工具&#xff0c;它通过智能代码补全、聊天式AI对话和代码生成等功能&#xff0c;帮助开发者提高编程效率。Claude AI则是一个强大的人工智能平台&a…

Vue.js组件开发指南

Vue.js组件开发涵盖多方面内容。从基础层面看&#xff0c;组件作为可复用的Vue实例&#xff0c;能通过多种方式注册&#xff0c;其props用于接收外部数据、data需为函数以保障数据独立。生命周期的各个钩子函数在组件不同阶段发挥作用。组件通信包括父子间的特定方式和非父子间…