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

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

文章目录

  • 技术选型说明
  • 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: localhost
    port: 6379
    database: 15
    password: 123456

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

spring:
  data:
    redis:
      host: localhost
      port: 6379
      database: 15
      password: 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/
    @Bean
    public 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-utils
  profiles:
    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 {
    @Test
    public 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.kler.cn/a/402994.html

相关文章:

  • 实验四:构建园区网(OSPF 动态路由)
  • RabbitMQ简单应用
  • Mac 修改默认jdk版本
  • 【ubuntu24.04.1最简洁安装方案】
  • 【Android、IOS、Flutter、鸿蒙、ReactNative 】实现 MVP 架构
  • 第T8周:Tensorflow实现猫狗识别(1)
  • [ 跨域问题 ] 前后端以及服务端 解决跨域的各种方法
  • FIFO架构专题-拼接方案及FIFO读清案例
  • 使用 Axios 拦截器优化 HTTP 请求与响应的实践
  • 论文阅读 SeedEdit: Align Image Re-Generation to Image Editing
  • 绿光一字线激光模组:工业制造与科技创新的得力助手
  • 下单抽奖领取商品奖品之后还能继续抽奖问题处理
  • Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
  • mysql中between and怎么用
  • 使用Go语言实现一个简单的HTTP服务器,提供静态文件服务。
  • Python 编程开发(01):Bash 命令行基本操作
  • HarmonyOS4+NEXT星河版入门与项目实战--------开发工具与环境准备
  • 研发效能DevOps: Vite 使用 Axios 实现数据双向绑定
  • C#实现数据采集系统-分组查询
  • 云原生学习
  • 图形学笔记 - 4. 几何 - 基本表示方法及曲线和曲面
  • 大数据入门-什么是Flink
  • 南京邮电大学算法设计-二叉树先序遍历算法动态演示
  • Springboot项目搭建(2)-用户详细信息查询
  • k8s搭建1.23版本
  • 从零开始深度学习:全连接层、损失函数与梯度下降的详尽指南