• 从JDK8升级到JDK17


    一、概述

    鉴于JDK8已经是老古董,还有性能问题,兼且各个公司已经不再维护1.8JDK,所以升级公司的核心产品之一的后端到JDK17是相对要紧的事情。

    通过升级到jdk17,具有以下好处:

    • 不再头疼同时适应两个jdk放下适应JDK8的负担
    • 在生产环境基本上只需要部署一个jdk即可
    • 具有更好的性能
    • 能够利用上更好更新的组件版本。例如springboot3,spring6.x都是基于jdk17的。
    • 更好的安全性。这对于项目很重要。因为许多客户会安排安全测试,过时的jdk是一个不好解决的问题。升级到jdk17能够更好解决这个问题
    • 更好的竞争能力。当我们的核心jdk是17的时候,毫无疑问比那些还沉滞在jdk8的竞争对手更好,尤其是功能相差不大的情况下。

    本次升级后端,大概耗费了一周的时间,其次httpsecurity耗费了比较多的时间。

    整体上,还算顺利。

     

    二、步骤详情

     

    总体上遵循以下步骤:

    (1) 升级准备

    (2) 确定spring组件版本

    spring.io上看了下,选择springboot-3.3.0

    (3) 确定其它组件版本

    (4) 升级有关代码

    (5) 调整其它配置

    (6) 解决有关异常

    (7) 测试

    (8) 完成升级

    2.1. 升级准备

    由于是一个大的版本升级,所以需要做以下准备:

    (1) 确认是否能升级

    除了前文提到的原因,还需要确定当前这个产品是否可以升级,毕竟JDK17JDK1.8不一样,且升级了JDK17后,有关的组件都要一起升级(是否有相关的版本,相关版本是否稳定?)

    考虑到我们的产品没有使用太多的三方软件,即使有,也都是流行的

    现在jdk17都已经发布了快3年了;有很多其它公司也升级到了jdk17;部分公司已经把他们的产品升级到JDK21了。

    结合这些因素,产品升级jdk17不存在技术障碍!

    之所以没有考虑立刻升级到JDK21,是因为其它很多产品都在JDK17,其次一次性到21,没有那么大把握。步子太大,会不会扯蛋了?

    虽然官方的文档说springboot3.3.x支持JDK22。但是由于三方组件的存在,导致不敢一次性迈出太大的步子。

    (2) git/svn上开一个分支,或者直接开一个新的仓库,不要影响现有的主干代码

    2.2. spring组件版本

    spring.io上可以看到可用的版本,本着使用最新可用版本的原则,选择了:

    • springboot-3.3.1 (ga)
    • spring-6.1.10 这是springboot限定的版本,所以只需要选择springboot版本即可

    按照spring惯例,当升级的时候,通常相关的组件都是一期升级的,所以总的来说,只要指定springboot版本即可。

     

    2.3.确定其它组件版本

     

    分类

    组件

    功能描述

    旧版本

    升级

    新版本

    说明

    数据存取

    druid-spring-boot-starter

    数据连接和连接池管理

    1.2.11

    1.2.23

    核心组件,必须升级

    jdbc驱动

    *

    jdbc连接

     

     

    主要看各个厂家,考虑到jdbc驱动都是比较成熟的,在jdk17中运行,问题应该也不大

    消息队列

    org.apache.rocketmq/rocketmq-client

    amqp

    5.1.0

     

    暂时不升级,这个需要较长时间的测试

    http请求

    httpclient,httpcore

    rest请求

    4.5.13

    5.3.1/5.2.4

    原来是:

    org.apache.httpcomponents/httpclient

    现在是:

    org.apache.httpcomponents.client5/httpclient5

    http

    javax.servlet/javax.servlet-api

    servlet

    4.0.1

     

    移除

    http

    jakarta.servlet/jakarta.servlet-api

    servlet

    6.0.0

     

    新增。用于替代javax.servlet-api

    JSON

    fastjson2

    JSON

    2.0.32

    2.0.51

    fastjson2bug较多,尽可能升级下

    JSON

    com.jayway.jsonpath/json-path

    JSON路径分析

    2.8.0

     

     

    ORM

    mybatis-spring-boot-starter

    orm

    2.2.2

    3.0.3

    不升级会导致mybatis有关bean初始化异常

    ORM

    pagehelper-spring-boot-starter

    分页

    1.4.3

    2.1.0

    mybatis依赖

    ORM

    jsqlparser

    sql解析

    4.2

    4.7

    pageHelper依赖

    XML

    javax.xml.bind/jaxb-api

    XML解析

    2.3.1

     

    暂时不可替代,不可删除,也不需要升级

    文档

    swagger

    文档

     

     

    从现有版本移除

    定时/调度

    quartz

    定时/调度

    2.3.2

     

     

    通用工具

    org.apache.commons/common-lang3

     

    3.12.0

    3.14.0

     

    通用工具

    org.apache.commons/commons-pool2

     

    2.9.0

    2.12.0

     

    通用工具

    commons-io/commons-io

     

    2.11.0

     

     

    通用工具

    commons-fileupload/commons-fileupload

     

    1.4

     

     

    存储

    minio

     

    8.2.1

     

     

    编译

    maven-compiler-plugin

    编译

    3.1

    3.13.0

     

    注:

    1. 主要考虑到核心组件即可,其它的小组件遇到了再解决。
    2. 有什么版本可用,可以访问https://mvnrepository.com,或则各个组件官网(一般是github),或者是国内镜像网站,例如阿里的https://developer.aliyun.com/mvn/search
    3. 部分组件版本必须在升级中调试后才可以确定

    2.4. 升级有关代码

    当更换了以下组件之后,需要尽快修改代码,修改的原因主要包含:

    (1) 配置变更

    主要是spring升级导致,可能需要修改配置。当然也可能是其它的组件

    (2) 包路径变更

    (3) 方法不存在

    (4) 方法过时

    这个需要特别注意-如果可能应该尽量把过时的方法移除掉,替换为正常的方法。

    2.4.1. 修改yml配置

    • 修改范围-spring.redis修改为spring.data.redis--这是spring要扩大spring.data的范围

          在spring.data的域名之下,有很多的内容,远不止redis.除了基本的JDBC,还有Rest,elasticsearch,jpd,ldap等等。

    • 添加参数(bean相关)

     

    spring:

      main:

        allow-circular-references: true

        allow-bean-definition-overriding: true

     

    spring6.1.10中,这两个属性默认是false.如果你的项目不存在循环引用,或者覆盖定义的情况,那么可以不添加.

    • 移除配置

          移除swagger配置-这个太垃圾,过分入侵,还浪费了自有的注释,增大程序员的工作量

    希望有直接能够利用javaDoc的类似组件。

        

    2.4.2. java基础类型有关的

    基础类型主要指Integer,Long,BigDecimal,BigInteger等等。

    jdk17中,许多方法已经被标注为过时(deprecated)

    (1) java基类 new Class("xxx")需要修改为 Class.valueOf("xxx")

    new Long("xx"),new Integer("xxx"),new Byte("xx"),new Short("")

    这些都要修改为对应的valueOf("xxx")

    jdk这么做,主要是出于性能考虑,尤其针对Integer

    (2) Class.newInstance()

    需要把这个替换为getDeclaredConstructor().newInstance()

    (3) Spring.Base64Util过时,改用apacheBase64

    (4) ruoyi自身的Base64,移除掉,避免和apache的冲突。

    (5) ruoyi自身的md5Util移除

    spring自身的改变来看,spring也逐渐向java标准和阿帕奇基金会靠近,一个是为了标准,其次是避免浪费

    时间,最后是不要给spring用户带来困扰。spring只要做好自己的就行了。

    ruoyi如果用于项目还是可以的,但是用于产品开发还是需要进行较多的改造。 因为产品要求更高的安全、适应度、性能等。

    2.4.3. servlet相关

    由于在jdk17中移除了javax的部分包,所以很多javax.xxx都需要修改jakarta.xxx

    这里主要包含:

    (1) javax.servlet

    (2) javax.annotation

    其它javax.net,javax.sql等则继续保留着。

     

    2.4.4. httpclient相关

    具体略,总之需要修改。

    httpclient5有重大变更:支持http2,异步支持,更好的连接池等

    2.4.5. spring-security

    这个改变比较大,在spring6.x主要通过注解和定义bean来实现spring-security配置,而在5.x中,则是通过扩展WebSecurityConfigurerAdapter来实现。

     

    @Configuration

    @EnableWebSecurity

    @EnableGlobalAuthentication

    @EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)

    public class SecurityConfig {

    }

     

     

    注意,不要去override已有的实现,否则配置还是比较麻烦的。

    spring的思路就是你可以改配置,改零件,但是不要改核心。如果要改核心,那么太费劲了。

    在这个类中实现以下几个bean即可:

    • AuthenticationProvider
    • AuthenticationManager
    • SecurityFilterChain
    • BCryptPasswordEncoder

    其中SecurityFilterChain是关键,这里主要配置白名单

    另外一个变化是,禁用了默认的logout,而是新增了一个/logout接口:

     

    /**

    * 执行退出

    * @param request

    * @return 

    * @since 1.5

    */

    @PostMapping("/logout")

    public AjaxResult logout(HttpServletRequest request) {

    try {

    LoginUser user=SecurityUtils.getLoginUser();

    String key=CacheConstants.LOGIN_TOKEN_KEY + user.getToken();

    redisCache.deleteObject(key);

    return AjaxResult.success();

    }

    catch(Exception e) {

    //如果有异常,则证明已经退出了,不要阻拦

    return AjaxResult.success();

    }

    }

     

    2.4.6. jsqlparser

    主要是因为pageHelper升级了。

    当然产品本身也有用到jsqlparser

    2.5. 调整其它配置

    主要是编译方面的配置。

    由于升级了jdk,包括核心组件maven-compiler-plugin所以有些原来的默认设置需要进行调整。

    2.5.1. 调整编译选项

    eclipse中,其实只需要设置pom.xml中配置即可,无需修改工程的环境配置。

     

    org.apache.maven.plugins

    maven-compiler-plugin

    3.13.0

    ${java.version}

    ${java.version}

    ${project.build.sourceEncoding}

    <parameters>trueparameters>

     

    红色部分添加上去,并设置为true

    如果不添加这个,那么spirng中很多需要通过反射获取信息的方法可能存在问题。

    因为这个选项会让java.java编译为.class的时候,保留方法的名称,而不是把方法名称随意修改为不认识的名称。

     

    2.6. 解决有关异常

     

    2.6.1. bean异常

    2.6.1.1. 循环引用和覆盖

    如前,主要新版本中,有些参数修改了默认值,所以修改如下:

    spring:

      main:

        allow-circular-references: true

        allow-bean-definition-overriding: true

     

    2.6.1.2. @Primary问题

    当有多个Datasource类型的Bean,或者类似其它的,则必须为Bean添加@Primary的注解,否则回报告异常。

    Parameter 0 of method sqlSessionFactory in com.ruoyi.framework.config.db.MyBatisConfig required a single bean, but 3 were found:

    触发异常的具体代码如下:

     

     

     

    而在以前的版本中不存在这个!

    解决方式有两个:

    (1) 在参数上简单添加@Qualifier("masterDataSource")  -- 解决了mybatis,但是还要解决quartz等等。放弃这个方法

    (2) 直接修改定义DataSource的地方,为主bean添加@Primary ,就用这个✔

     

    2.6.2. jackson序列化异常

    1.Integer无法作为key的异常

    无法处理key类型不是String类型,例如如果是以下类型的JSON

     

    {

    "batchDetail": {

       1: "good"

    }

    }

     

    在序列化的时候会报告异常:

    org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')

     

    必须自定义HttpMessgeConverter,以便可以自定义处理这种类型的key。

    特别注意的是配置中需要添加注解@EnableWebMvc。

     

    如果不加这个注解,那么配置HttpMessageConvert的并不会生效,也意味着自定义的序列化,反序列化无效。

    但是加了这个注解,又会导致security的WebSecurityCustomizer中的自定义RequestMatcher出现一个问题:

    RequestContextHolder.currentRequestAttributes()返回空值

    这个自定义RequestMatcher是实现在线实时白名单的关键,但它需要依赖于线程中的属性。

    现在属性是空的,怎么办?幸好可以通过以下语句解决:

    RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request))

    这个奇怪的问题,不确认是当前6.1.8/3.3.1的bug还是因为我自己不够熟悉配置导致。

     

    2.String[]反序列化异常

    6/24继续测试,又发现了一个bug。

    有个对象的属性类型是String[],前端传递的是"",这在以往的版本没有问题。但是现在报错了,具体什么忘了。

    于是又写了个反序列化器,继承自JsonDeserializer,类型是String[].

    完了之后,在字段上添加注解@JsonDeserialize(using=xxx.class)

     

    6/26继续测试,发现类似问题。再那么配置下去,不知道要写多少个反序列化器了,直接统一下配置:

    在webMvcConfigurer实现类中添加bean定义

     

    复制代码
     @Override
        public void configureMessageConverters(List> converters) {
            converters.add(new ByteArrayHttpMessageConverter());
            converters.add(new StringHttpMessageConverter());
            converters.add(new ResourceHttpMessageConverter());
            converters.add(new ResourceRegionHttpMessageConverter());
            MappingJackson2HttpMessageConverter jconverter = new MappingJackson2HttpMessageConverter();
            DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            ObjectMapper objectMapper=jconverter.getObjectMapper();
            objectMapper.setDateFormat(dateFormat);
            //添加序列化和反序列特性
            objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);        
            SerializerProvider p= objectMapper.getSerializerProvider();
            p.setNullValueSerializer(new JsonSerializer() {
                @Override
                public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                    jsonGenerator.writeString("");
                }
            });
            p.setDefaultKeySerializer(new CustomJsonKeySerializer());
            converters.add(jconverter);
        }
    
    复制代码

     

     

     

    这里的关键--如果自定义ObjectMapper,但是不配置,很多特性是有缺省响应的,但是不包含把空字符串转为NULL对象,所以需要添加:

    objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);

     

    2.6.3. factoryBeanObjectType异常

    经过定位,这是mybatis没有升级导致的。

    注:一开始的时候,并没有立刻要升级mybatis,虽然意识到了,但是并没有那么做。

     

    2.7. 测试

    (1) 每个地方都需要测试

    (2) 反复测试

    这是总的原则。

    测试需要持续较长时间,严格而言,需要再耗费一个月左右。

     

    从目前来看,总体是可用!

    从性能上看,JDK17的程序的确响应更快一些,从页面的响应也可以看出来!

     

    2.8. 完成升级

       

        完成升级后,关闭原来git上代码的权限,设置为只读,并通知有关人。

     

  • 相关阅读:
    vue组件间传值之插槽
    性能测试系列二 何时介入性能测试
    【牛客网面试必刷TOP101】链表篇(三)
    手把手教你解决循环依赖,一步一步地来窥探出三级缓存的奥秘
    Comfyui|AnimateDiff生成动画基础使用方法
    高云FPGA系列教程(6):ARM定时器使用
    error: reference to ‘byte‘ is ambiguous使用QtCharts报的错误
    jquery datatable+bootstrap popover在表格里弹出对话框时只在表格内部,而不是外部
    Linux_7_软件管理
    谷歌的SRE和开发是如何合作的
  • 原文地址:https://www.cnblogs.com/lzfhope/p/18262463