相关推荐recommended
SpringBoot2.7升级项目到Springboot3.1踩坑指南(jdk17jdk21)
作者:mmseoamin日期:2024-02-22

文章目录

    • 概要
    • 国内顶级开源项目升级情况
    • 适配SpringBoot3指南
    • SpringBoot3升级要点
      • 1、jdk17变动(如javax)
      • 2、redis修改spring.redis.host ===> spring.data.redis.host
      • 3、SpringCloudApplication注解被删除
      • 4、不兼容升级import java.servlet====>import jakarta.servlet
      • 5、swagger集成 弃用springfox--->springdoc不兼容升级
      • 6、动态数据源baomidou的dynamic-datasource依赖变动
      • 7、Spring Framework 6.0 中删除了对 Apache HttpClient 支持(RestTemplate受影响)
      • 8、SpringBoot3.0整合RocketMQ时出现未能加载bean文件
      • 9、springboot3默认依赖 elasticsearch从7.x升级到8.x
      • 10、springboot 3.2 openFeign加载失败暂未解决 (3.1没问题)
      • 11、hutool5.8-->hutool6.0升级要要点
      • 12、Centos7使用jdk21报错
      • 13、@Async注解报错Invalid return type for async method (only Future and void

        概要

        由于SpringBoot3.x全面拥抱JDK17,兼容jdk21,jdk17乃是大势所趋。这里是从SpringBoot2.7-->SpringBoot3.1踩坑指南。

        提前阅读:jdk8升级JDK17避坑指南(适用于SpringBoot2.3—SpringBoot2.7升级)

        国内顶级开源项目升级情况

        国内顶级开源项目升级到springBoot3情况,可以作为升级SpringBoot3的风向标。仅对比国内规模使用,落地过万企业的开源项目

        参考:国内顶级开源项目:芋道、ruoyi、JeecgBoot、pig、SpringBlade功能对比

        评价项/项目名yudao-cloudRuoyi-CloudRuoYi-Cloud-PlusDante CloudpigbladexJeecgBoot
        官网芋道yudao-cloud 开发指南若依plus-doc.dromara.orgDante Cloudpig4cloudbladex.cn、看云-SpringBlade开发手册JeecgBoot
        源码收费免费免费免费免费免费 + 收费(3999)免费 + 收费(5000)免费 + 收费(100000)
        文档收费文档收费免费、视频收费文档免费、视频收费免费免费、授权收费文档收费文档免费、授权收费
        githubyudao-cloudRuoYiRuoYi-Vue-PlusDante CloudpigSpringBladejeecg-boot
        giteeyudao-cloudRuoYiRuoYi-Vue-PlusDante Cloud暂无SpringBladejeecg-boot
        jdk17分支master-jdk21RuoYi-Cloud-Plus 2.Xdante-cloud 3.1.Xpig jdk17jeecg-boot/springboot3

        适配SpringBoot3指南

        • 参考1-微信公众号-这可能是最全的SpringBoot3新版本变化了!、
        • 参考2-SpringBoot官网-Spring Boot 3.0 Release Notes、
        • 参考3-微信公众号-Swagger升级指南:Swagger2与Swagger3注解差异揭秘、
        • 参考4-微信公众号-Dante Cloud 3.2.0.0 发布,首个适配 Spring Boot 3.2版本及经验分享
        • 参考5-JeecgBoot 文档中心-升级SpringBoot3

          SpringBoot3升级要点

          前提说明,建议先完成springboot2.x—>springBoot2.7.x+jdk17的适配,这里升级难度会小很多。参考:文章最前面的文章。

          1、jdk17变动(如javax)

          详见: jdk8升级JDK17避坑指南(适用于SpringBoot2.3—SpringBoot2.7升级)

          • 模块化对反射的影响==>对系统类的反射增加了限制,需要打开限制增加jvm启动参数add-opens,自己写的类,可以正常使用反射。
          • 删除sun.misc 下的包,如sun.misc.BASE64Encoder==>java.util.Base64替换
          • 删除JAXB、soup相关==>maven仓库上面有新的maven坐标,引入新依赖即可
          • 删除javax.annotation==>maven仓库上面有新的maven坐标,引入依赖即可

            2、redis修改spring.redis.host ===> spring.data.redis.host

            redis配置命令空间进行了修改,需要注意。

            参考:Spring Boot3.0(九):整合Redis

            --- # redis 配置,注意springboot 3.x 有 data,2.x 没有 data
            ## spring.redis.host ===> spring.data.redis.host
            spring:
              data:
                redis:
                  host: 10.16.58.180
                  port: 6379
                  password: Admin123
                  database: 6      
            

            3、SpringCloudApplication注解被删除

            使用@SpringBootApplication替换

            4、不兼容升级import java.servlet====>import jakarta.servlet

            servlet捐献给社区,为了避免版权问题,修改了包名,导致不兼容.

            • javax.servlet===> jakarta.servlet
            • javax.validation ===> jakarta.validation
            • javax.annotation ===> jakarta.annotation
            • javax.mail ===> jakarta.mail
            • javax.websocket ==> jakarta.websocket
               
                          
                              jakarta.servlet
                              jakarta.servlet-api
                              6.0.0
                            
              

              5、swagger集成 弃用springfox—>springdoc不兼容升级

              springfox不维护了,springboot3使用springdoc,并启用openapi3.0,相关注解进行了变化。

              参考:spring boot 3 整合 swagger3、

              参考:Swagger升级指南:Swagger2与Swagger3注解差异揭秘、

              参考:OpenApi3.0注解说明

              注解作用swagger2swagger2示例swagger3-openApi3.0swagger示例替换
              用于Controller@Api@Api(value = "/app/child/v2", tags = "儿童档案")@Tag@Tag(name = "/app/child/v2", description = "儿童档案")@Api(value = "User Management", description = "Operations pertaining to users")—>@Tag(name = "User Management", description = "Operations pertaining to users")、@Api(tags = "小程序端Core Controller")—>@Tag(description = "小程序端Core Controller")
              用于Controller接口@ApiOperation@ApiOperation(value = "新增儿童档案绑定监护人", httpMethod = "POST", produces = "application/json")@Operation @Operation(summary = "新增儿童档案绑定监护人", method = "POST")@ApiOperation(value = "根据儿童证件号码查询儿童档案", httpMethod = "POST", produces = "application/json")–>@Operation(summary = "根据儿童证件号码查询儿童档案", method = "POST")
              用于Controller接口参数注解@ApiParam或@ApiImplicitParam@ApiParam("预约id,字段名:personApptId,默认无")@Parameter@Parameter(description = "预约id,字段名:personApptId,默认无", required = true) @ApiParam(value = "追溯码 形如:81900920216939751445,max=32", defaultValue = "81900920216939751445")—>@Parameter(description = "追溯码 形如:81900920216939751445,max=32", example = "81900920216939751445")
              参数隐藏@ApiIgnore@ApiIgnore HttpServletRequest request///
              实体字段@ApiModelProperty@ApiModelProperty("接种人员-姓名(冗),max=32")、@ApiModelProperty(notes = "The database generated user ID")@Schema@Schema(description = "主键 自增")@ApiModelProperty(value—>@Schema(description、@ApiModelProperty(hidden = true)–>@Schema(hidden = true、@ApiModelProperty(value = "更新人员", example = "张三",hidden = true)—>@Schema(description = "更新人员", example = "张三",hidden = true)
              实体类@ApiModel@ApiModel("接种人员-姓名(冗),max=32")、@ApiModelProperty(notes = "The database generated user ID")@Schema@Schema (description = "根据儿童证件号码查询儿童档案")")@ApiModel(value—>@Schema(description、@ApiModel(description —>@Schema(description
              // idea正则替换01
              import io.swagger.annotations.Api;
              import io.swagger.v3.oas.annotations.tags.Tag;
              @Api\(tags = "([^\"]+)", hidden = ([^\"]+), description = "([^\"]+)"\)
              @Tag(name = "", description = "")
              @Api\(value = "([^\"]+)", tags = "([^\"]+)"\)
              @Tag(name = "", description = "")
              @Api\(tags = "([^\"]+)", description = "([^\"]+)"\)
              @Tag(name = "", description = "")
              @Api\(tags = "([^\"]+)"\)
              @Tag(name = "")
              // idea正则替换02
              import io.swagger.annotations.ApiOperation;
              import io.swagger.v3.oas.annotations.Operation;
              @ApiOperation\(value = "([^\"]+)", notes = "([^\"]+)", position = ([^\"]+)\)
              @Operation(summary = "", description= "")
              @ApiOperation\(value = "([^\"]+)", httpMethod = "([^\"]+)", produces = "([^\"]+)"\)
              @Operation(summary = "", method = "")
              @ApiOperation\(value = "([^\"]+)", notes = "([^\"]+)"\)
              @Operation(summary = "", description = "")
              @ApiOperation\(value = "([^\"]+)", httpMethod = "([^\"]+)"\)
              @Operation(summary = "", method = "")
              @ApiOperation\(value = "([^\"]+)"\)
              @ApiOperation\("([^\"]+)"\)
              @Operation(summary = "")
              // idea正则替换03
              import io.swagger.annotations.ApiModel;
              import io.swagger.v3.oas.annotations.media.Schema;
              @ApiModel\(value = "([^\"]+)"\)
              @Schema(description = "")
              @ApiModel\(description = "([^\"]+)"\)
              @Schema(description = "")
              // idea正则替换04
              import io.swagger.annotations.ApiModelProperty;
              '';
              @ApiModelProperty\(value = "([^\"]+)", example = "([^\"]+)", required = ([^\"]+)\)
              @Schema(description = "", example = "", required= )
              @ApiModelProperty\(value = "([^\"]+)", example = "([^\"]+)", hidden = ([^\"]+)\)
              @Schema(description = "", example = "", hidden = )
              @ApiModelProperty\(value = "([^\"]+)", example = "([^\"]+)"\)
              @Schema(description = "", example = "")
              @ApiModelProperty\(value = "([^\"]+)", hidden = ([^\"]+)\)
              @Schema(description = "", hidden = )
              @ApiModelProperty\(value = "([^\"]+)"\)
              @Schema(description = "")
              @ApiModelProperty\(hidden = ([^\"]+)\)
              @Schema(hidden = )
              @ApiModelProperty\("([^\"]+)"\)
              @Schema(description = "")
              @ApiModelProperty\(value="([^\"]+)"\)
              @Schema(description = "")
              // idea正则替换05
              import io.swagger.annotations.ApiParam;
              import io.swagger.v3.oas.annotations.Parameter;
              @ApiParam\(value = "([^\"]+)", example = "([^\"]+)", defaultValue = "([^\"]+)"\)
              @Parameter(name = "", example = "", description="")
              @ApiParam\(value = "([^\"]+)", defaultValue = "([^\"]+)"\)
              @ApiParam\(value = "([^\"]+)", example = "([^\"]+)"\)
              @Parameter(name = "", example = "")
              @ApiParam\(value = "([^\"]+)"\)
              @ApiParam\("([^\"]+)"\)
              @Parameter(name = "")
              // idea正则替换06
              import springfox.documentation.annotations.ApiIgnore;
              import io.swagger.v3.oas.annotations.Hidden;
              @ApiIgnore
              @Hidden
              @ApiIgnore HttpServletResponse response
              @ApiIgnore HttpServletRequest request
              // idea正则替换07
              @ApiImplicitParams
              @Parameters
              @ApiImplicitParam\(name = "([^\"]+)", value = "([^\"]+)",required = ([^\"]+), example = "([^\"]+)", paramType = "query"\)
              @Parameter(name = "", description = "", required = , example = "", in = ParameterIn.QUERY)
              @ApiImplicitParam\(name = "([^\"]+)", value = "([^\"]+)", required = ([^\"]+), paramType = "query"\)
              @Parameter(name = "", description = "", required = true, in = ParameterIn.QUERY)
              @ApiImplicitParam\(name = "([^\"]+)", value = "([^\"]+)", dataType = "([^\"]+)", required = ([^\"]+)\)
              @Parameter(name = "", description = "", schema = @Schema(type = ""), required = )
              @ApiImplicitParam\(name = "([^\"]+)", value = "([^\"]+)", dataType = "([^\"]+)"\)
              @Parameter(name = "", description = "", schema = @Schema(type = ""))
              @ApiImplicitParam\(name = "([^\"]+)", value = "([^\"]+)", paramType = "form"\)
              @Parameter(name = "", description = "", in = ParameterIn.QUERY)
              @ApiImplicitParam\(name = "([^\"]+)", value = "([^\"]+)", dataType = "__file", paramType = "form"\)
              @Parameter(name = "", description = "", example = "__file", in = ParameterIn.QUERY)
              @ApiImplicitParam\(value = "([^\"]+)"\)
              @Parameter(description = "")
              

              swaggr2实例

              // Swagger2 实体类
              @Getter
              @Setter
              @NoArgsConstructor
              @ApiModel(value = "根据儿童证件号码查询儿童档案")
              public class ChildGetByChildNoVo implements Serializable {
                  @ApiModelProperty(value = "身份证")
                  private String childNo;
                  private Long personId;
              }
              // Swagger2 Controller
              @AllArgsConstructor
              @RestController
              @RequestMapping("/app/child/v2")
              @Api(value = "/app/child/v2", tags = "儿童档案")
              public class ChildController extends BaseController 
                @ApiOperation(value = "根据监护人ID分页查询被监护人", httpMethod = "GET", produces = "application/json")
                  @GetMapping("/getByPersonId")
                  @ApiImplicitParam(value = "预约id")
                  public AjaxResult getByPersonId(@RequestParam(value = "pageNum",required = false,defaultValue = "1") Integer pageNum,
                                                @ApiParam("预约id,字段名:personApptId,默认无") @RequestParam(value = "personApptId") Long personApptIdpersonId,
                                                 @ApiIgnore HttpServletRequest request
                                                 ){
                      IPage page = new Page<>(pageNum,pageSize);
                      IPage pageData = childService.getByPersonId(page,personId);
                      TableDataInfo rspData = new TableDataInfo();
                      rspData.setCode(200);
                      rspData.setRows(pageData.getRecords());
                      rspData.setMsg("查询成功");
                      rspData.setTotal(pageData.getTotal());
                      return AjaxResult.success(rspData);
                  }
               
               @PostMapping("/getCode")
                  @Operation(summary = "获取预约码", method = "POST")
                  @ApiImplicitParam(value = "预约id")
                  @ApiImplicitParams({
                          @ApiImplicitParam(name = "personId", value = "用户id", paramType = "form"),
                          @ApiImplicitParam(name = "apptId", value = "预约id", paramType = "form"),
                          @ApiImplicitParam(name = "collectLocationId", value = "采样点id", paramType = "form"),
                  })
                  public String getCode(Long personId, Long apptId, Long collectLocationId) {
                      return vficPersonService.generateApptQrCode(apptId, personId, collectLocationId);
                  }
              }
              

              swagger3示例

               
              

              6、动态数据源baomidou的dynamic-datasource依赖变动

              经过测试,该条也可以不升级

              参考1:baomidou dynamic-datasource、

              参考2:kancloud tracy5546 dynamic-datasource

              
              
                com.baomidou
                dynamic-datasource-spring-boot-starter
                ${version}
              
              
              
                com.baomidou
                dynamic-datasource-spring-boot3-starter
                ${version}
              
              

              7、Spring Framework 6.0 中删除了对 Apache HttpClient 支持(RestTemplate受影响)

              升级到SpringBoot3发现依赖提示缺少:rg.apache.httpcomponents:httpclient,一些三方库可能依赖httpclient,就需要自己手工引入依赖。三方包如:spring-data-elasticsearch5、nacos-client 1.4.6、weixin-java-pay 4.0.0、htmlunit 3.6

              参考: Spring-Boot-3.0-Migration-Guide#apache-httpclient-in-resttemplate、

              
              
                  org.apache.httpcomponents
                  httpclient
                  4.5.14
              
              
              
                  org.apache.httpcomponents.client5
                  httpclient5
                  5.3
              
              

              8、SpringBoot3.0整合RocketMQ时出现未能加载bean文件

              springboot2.x使用rocketmq没有问题,springboot3出现,required a bean of type ‘org.apache.rocketmq.spring.core.RocketMQTemplate’ that could not be found.

              参考1:SpringBoot3.0整合RocketMQ时出现未能加载bean文件

              参考2:Spring boot 3.0整合RocketMQ及不兼容的问题

              
                          org.apache.rocketmq
                          rocketmq-spring-boot-starter
                          
                          2.2.3
                      
              
              // 定义个配置类,引入也可以:
              @Configuration
              @Import({RocketMQAutoConfiguration.class})
              public class RocketMQConfig {
              }
              

              9、springboot3默认依赖 elasticsearch从7.x升级到8.x

              • 配置文件地址修改:spring.elasticsearch.rest.uris==>spring.elasticsearch.uris
              • springboot3要求elasticsearch必须升级到8.x,不能使用7.x,否则报错:缺失响应头X-Elastic-Product(Elasticsearch)
              • 废弃:high client客户端
              • 要求ElasticSearch必须8.x

                10、springboot 3.2 openFeign加载失败暂未解决 (3.1没问题)

                启动失败 feign导致 not annotated with HTTP method type (ex. GET, POST)

                • https://blog.csdn.net/nailsoul/article/details/105223740
                • https://www.jianshu.com/p/11b4cbc8951a
                • https://juejin.cn/post/7112414513550491656

                  11、hutool5.8–>hutool6.0升级要要点

                  说明常见类,如果找不到,请看源码注释,注释上面写的有,或者全局搜索即可,无需多说。

                  • ServletUtil–>JakartaServletUtil
                  • hutool类变化说明: 【6.0.0】升级到6.0不兼容情况汇总,大家一起来统计维护呀、
                  • 国密相关SmUtil,使用6.0x问题,参加:hutool 5.x 和 hutool 6.x crypto 国密SMUtil 模块放在一个工程中会产生冲突

                    12、Centos7使用jdk21报错

                    java: /lib64/libc.so.6: version `GLIBC_2.14’ not found (required by /usr/local/java/jdk-21.0.2+13/bin/…/lib/libjli.so)

                    原因,Centos6缺失GLIBC_2.14,报错缺失2.14、2.15解决办法相同。

                    只需要安装最高版本,自动安装低版本,比如:安装2.17.自动安装2.14

                    参考1:version `GLIBC_2.14’ not found 问题解决

                    参考2:解决 /lib64/libc.so.6: version `GLIBC_2.15’ not found 问题

                    最新已经是2.38了,可以直接安装:wget --no-check-certificate https://ftp.gnu.org/gnu/glibc/glibc-2.38.tar.gz,安装前确保安装的有gcc。