jdk8升级JDK17避坑指南(适用于SpringBoot2.3---SpringBoot2.7升级)

jdk8升级JDK17避坑指南

  • jdk8升级JDK17避坑指南
    • 一、模块化对反射的影响
      • 1.1 示例通过反射获取String的value值
      • 1.2 示例Orika JavaBean映射jdk17报错
    • 二、删除的内置库
      • 2.1、删除JAXB、soup相关
      • 2.2、删除javax相关包
      • 2.2.1、删除javax.annotation重命名为jakarta.annotation
      • 2.2.2、删除javax.validation重命名为jakarta.validation
      • 2.4、删除sun.misc.* 下的包,如sun.misc.BASE64Encoder
    • 三、字体相关报错
    • 四、jvm参数修改
    • 五、jdk21运行打包java.lang.NoSuchFieldError:com.sun.tools.javac.tree.JCTree$JCImport

jdk8升级JDK17避坑指南

随着SpringBoot2.7的发布,支持jdk8~jdk21。Springboot3.X发布,最低需要jdk17。升级jdk17是大势所趋。
本文升级适用于SpringBoot2.7.x从jdk8升级到jdk17操作指南,成功把ruoyi4.x微服务的jdk8升级到jdk17,应用到生产。

参考1:重磅!Spring Boot 2.7 正式发布、
参考2:hutool-希望Hutool能支持下JDK8~JDK17的所有版本
参考3:aliyun-一文详解|从JDK8飞升到JDK17,再到未来的JDK21
参考4:程序员DD-从 Java 8 升级到 Java 17 踩坑全过程,建议收藏!
参考5:老卫waylau-JDK

一、模块化对反射的影响

正式开发业务系统,使用反射修改jdk自带类场景较少。即使修改,通过–add-opens的jvm参数,即可解决该问题。

由于jdk9增加的模块化设计,导致对系统内置类反射受到限制,出现类似的错误,自己开发的类没有影响。

1.1 示例通过反射获取String的value值

 public static void main(String[] args) {
        // jdk17使用反射,无需修改即可正常使用的示例
        //在 Java8 中,没有人能阻止你访问特定的包;只要 setAccessible(true) 就可以了
        NucPerson nucPerson = new NucPerson();
        nucPerson.setPersonName("张三");
        ReflectUtil.setFieldValue(nucPerson, "personPhone", "18800001111");//hutool5中工具类
        System.out.println(nucPerson.getPersonPhone());
        System.out.println(ReflectUtil.getFieldMap(NucPerson.class));

        // jdk17使用反射系统模块的类,必须要增加--add-opens的反射示例
        // Java9 模块化以后,一切都变了,只能通过 --add-exports 和 --add-opens 来打破模块封装
        Object stringValue = ReflectUtil.getFieldValue(nucPerson.getPersonName(), "value");
        System.out.println(stringValue);
    }
java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.Map sun.reflect.annotation.AnnotationInvocationHandler.memberValues accessible: module java.base does not "opens sun.reflect.annotation" to unnamed module @52d455b8
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) ~[na:na]
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) ~[na:na]
	at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178) ~[na:na]
	at java.base/java.lang.reflect.Field.setAccessible(Field.java:172) ~[na:na]
	at cn.hutool.core.util.ReflectUtil.setAccessible(ReflectUtil.java:966) ~[hutool-core-5.7.19.jar:na]
	at cn.hutool.core.util.ReflectUtil.getFieldValue(ReflectUtil.java:266) ~[hutool-core-5.7.19.jar:na]
	at cn.hutool.core.util.ReflectUtil.getFieldValue(ReflectUtil.java:234) ~[hutool-core-5.7.19.jar:na]
	at cn.hutool.core.annotation.AnnotationUtil.setValue(AnnotationUtil.java:215) ~[hutool-core-5.7.19.jar:na]

解决:增加jvm启动参数(java虚拟机启动参数)

--add-opens java.base/sun.reflect.annotation=ALL-UNNAMED

汇总:

        -Djdk.home=/Library/Java/JavaVirtualMachines/openjdk-18.0.1.1/Contents/Home
        -Xms24m
        -Xmx768m
        -DTopSecurityManager.disable=true
	--add-exports=java.desktop/com.sun.java.swing.plaf.gtk=ALL-UNNAMED
	--add-exports=java.desktop/sun.awt=ALL-UNNAMED
	--add-exports=jdk.internal.jvmstat/sun.jvmstat.monitor.event=ALL-UNNAMED
	--add-exports=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED
	--add-exports=java.desktop/sun.swing=ALL-UNNAMED
	--add-exports=jdk.attach/sun.tools.attach=ALL-UNNAMED
	--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED
	--add-opens=java.desktop/javax.swing.plaf.synth=ALL-UNNAMED
	--add-opens=java.base/java.net=ALL-UNNAMED
	--add-opens=java.base/java.lang.ref=ALL-UNNAMED
	--add-opens=java.base/java.lang=ALL-UNNAMED
	--add-opens=java.desktop/javax.swing=ALL-UNNAMED
	--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED
	-XX:+IgnoreUnrecognizedVMOptions
	-XX:+HeapDumpOnOutOfMemoryError
	-XX:HeapDumpPath=/Users/duandazhi/Library/Application Support/VisualVM/2.1.2/var/log/heapdump.hprof

--add-opens--add-exports区别,opens是深度反射,可以打开对私有变量、成员的反射;exports只能打开公有变量的反射。总结:使用--add-opens肯定没问题。
jdk8升级JDK17避坑指南(适用于SpringBoot2.3---SpringBoot2.7升级)_第1张图片
总结:模块化需要增加的JVM打开反射参数,对常规业务系统影响不大,毕竟对系统类反射操作很少,对自己开发的类进行反射操作并没有限制。

1.2 示例Orika JavaBean映射jdk17报错

1、BeanUtils常用的Bean映射工具,包含apache的BeanUtils和spring的BeanUtils,底层使用简单,但底层反射效率比较低。
2、BeanCopier,基于cglib的BeanCopier直接生成字节码文件.class文件,效率有很大提升。但是,当属性名称和属性类型存在差异时,CGLib实现的BeanCopier会退化成暴力反射,性能就会低下。一般配合工具框架使用,如dubbo。
3、Orika,底层采用了javassist类库生成Bean映射的字节码,后直接加载执行生成的字节码文件,速度比反射快很多,支持嵌套。

参考1:Bean复制选型
BeanUtils、BeanCopier、Dozer、Orika 哪家强? 、
Java常见bean mapper的性能及原理分析1、
Java常见bean mapper的性能及原理分析2、
常见Bean拷贝框架使用姿势及性能对比、
对比性能:反射类(各类BeanUtils、Dozer)<<动态生成字节码(BeanCopier、Orika)<<直接GetSet(Mapstruct.plus)
参考2:报错参数
升级JDK1.8 到 JDK17 时访问报Unable to make protected native ??? .clone() 的异常、
DozerBeanMapper对象之间相同属性名赋值、
Orika User Guide、

//BeanMapper.java
import java.util.List;

/**
 * 类型copy接口
 *
 * @author dazers
 * @date 2017/12/4 下午5:27
 * @see java.util.function.Function
 * @see MapOrObject
 */
public interface BeanMapper {
    /**
     * 对象之间copy,可以是相同类型,也可以是不同类型;但不能是集合
     *
     * @param srcObj
     * @param targetObj
     * @return  dest
     */
    <S, D> D copy(S srcObj, D targetObj);

    /**
     * copy集合
     * 

* eg:List bssOrderVos = beanMapper.copyList(bssOrders, BssOrderVo.class); * * @param srcObjs 原始集合 * @param destClz 要转换之后的类型 * @param * @param * @return */ <S, D> List<D> copyList(List<S> srcObjs, Class<D> destClz); } @Configuration class CustomConfig { @Bean(name = "beanMapper") public BeanMapper registUtilsBeanMapper() { //多个Bean复制实现类,使用其中一个 ///return new DozerBeanMapperImpl(); return new OrikaBeanMapperImpl(); } }

//OrikaBeanMapperImpl.java
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.converter.builtin.CloneableConverter;
import ma.glasnost.orika.converter.builtin.PassThroughConverter;
import ma.glasnost.orika.impl.DefaultMapperFactory;

import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * 复制对象属性的工具类.
 *
 * @author Lingo
 * 

* orika */ public class OrikaBeanMapperImpl implements BeanMapper { /** * 实例. */ private static final MapperFacade MAPPER; static { //---------------------------这段代码jdk8运行没有问题,jdk17报错 没有使用反射copy的权限------------------ /** * Caused by: java.lang.reflect.InaccessibleObjectException: * Unable to make protected native java.lang.Object java.lang.Object.clone() * throws java.lang.CloneNotSupportedException accessible: * module java.base does not "opens java.lang" to unnamed module * @see CloneableConverter#CloneableConverter 报错没有反射权限 */ // 如果src中属性为null,就不复制到dest MapperFactory mapperFactory = new DefaultMapperFactory.Builder() .mapNulls(false).build(); // 如果属性是Object,就只复制引用,不复制值,可以避免循环复制 mapperFactory.getConverterFactory().registerConverter( new PassThroughConverter(Object.class)); MAPPER = mapperFactory.getMapperFacade(); } /** * 把src中的值复制到dest中. * src 和 dest 对象均不能为空 */ @Override public <S, D> D copy(S srcObj, D destObj) { Objects.requireNonNull(srcObj, "copy src 不能为null"); Objects.requireNonNull(destObj, "copy dest 不能为null"); if (srcObj instanceof Collection || destObj instanceof Collection) { throw new Exception("copy对象不能是集合,集合copy请使用 copyList方法"); } MAPPER.map(srcObj, destObj); return destObj; } /** * 复制list. */ @Override public <S, D> List<D> copyList(List<S> srcObjs, Class<D> destClz) { return MAPPER.mapAsList(srcObjs, destClz); } }

//DozerBeanMapperImpl.java
import org.dozer.DozerBeanMapper;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * 简单封装Dozer, 实现深度转换Bean<->Bean的Mapper.实现:
 * 

* 1. 持有Mapper的单例. 2. 返回值类型转换. 3. 批量转换Collection中的所有对象. 4. * 区分创建新的B对象与将对象A值复制到已存在的B对象两种函数. *

* DozerBeanMapper对象之间相同属性名赋值 http://blog.csdn.net/luo201227/article/details/24021791 * * @author calvin */ public class DozerBeanMapperImpl implements BeanMapper { /** * 持有Dozer单例, 避免重复创建DozerMapper消耗资源. */ private static DozerBeanMapper dozer = new DozerBeanMapper(); /** * 基于Dozer转换对象的类型. */ public <T> T map(Object source, Class<T> destinationClass) { return dozer.map(source, destinationClass); } /** * 基于Dozer转换Collection中对象的类型. */ public <T> List<T> mapList( @SuppressWarnings("rawtypes") Collection sourceList, Class<T> destinationClass) { List<T> destinationList = new ArrayList<T>(); for (Object sourceObject : sourceList) { T destinationObject = dozer.map(sourceObject, destinationClass); destinationList.add(destinationObject); } return destinationList; } /** * 基于Dozer将对象A的值拷贝到对象B中. * src 和 dest 对象均不能为空 */ @Override public <S, D> D copy(S sourceObj, D destinationObject) { Objects.requireNonNull(sourceObj, "原对象不能为空!"); Objects.requireNonNull(destinationObject, "目标对象不能为空!"); if (sourceObj instanceof Collection || destinationObject instanceof Collection) { throw new RrException("copy对象不能是集合,集合copy请使用 copyList方法"); } dozer.map(sourceObj, destinationObject); return destinationObject; } @Override public <S, D> List<D> copyList(List<S> srcObjs, Class<D> destClz) { return mapList(srcObjs, destClz); } }

二、删除的内置库

从jdk8~jdk17 有删除部分类,但都有对应的开源平替库,只需要找到对应库的maven依赖即可,因此影响可控。

高版本最坑应该是这一条:
Java 11 :移除JavaEE和CORBA模块

对于Java EE和CORBA模块在Java 9开始就不推荐使用了。而从Java 11开始正式删除了这部分内容,所以当升级到Java 11或更高的版本的话,务必要先更急以下内容相关的代码:
移除的包:

  • javax.xml.ws (JAX-WS)
  • javax.xml.bind (JAXB)
  • javax.xml.soap
  • javax.activation (JAF)
  • javax.validation
  • javax.mail
  • javax.xml.ws.annotation (Common Annotations)
  • java.corba (CORBA)
  • java.transaction (JTA)
  • java.se.ee (以上6个模块的聚合模块)

移除的工具:

  • wsgen and wsimport (from jdk.xml.ws)
  • schemagen and xjc (from jdk.xml.bind)
  • idlj, orbd, servertool, and tnamesrv (from java.corba)

2.1、删除JAXB、soup相关

比如javax.xml.bind.annotation.XmlElement、javax.xml.soap.SOAPElement相关包的类就不存在了,需要手工引入。

我的项目里面就使用到了webservice,即:jaxb(实体类和xml转换)、soapwebservice的http网络请求。
受到影响的hutool工具类cxf相关:JAXBUtilSoapClientJakartaServletUtilMailUtil全部使用jakarta命名;
业务场景,系统请求外部接口使用是webservice,实体类和xml转换需要用JAXBUtil、发送Http请求使用SoapClient

        
        
        <dependency>
            <groupId>com.sun.xml.bindgroupId>
            <artifactId>jaxb-implartifactId>
            <version>2.3.9version>
        dependency>
        
        <dependency>
            <groupId>org.glassfish.jaxbgroupId>
            <artifactId>jaxb-runtimeartifactId>
            <version>2.4.0-b180830.0438version>
        dependency>


        
        
        
        <dependency>
            <groupId>javax.xml.soapgroupId>
            <artifactId>javax.xml.soap-apiartifactId>
            <version>1.4.0version>
        dependency>
        <dependency>
            <groupId>com.sun.xml.messaging.saajgroupId>
            <artifactId>saaj-implartifactId>
            <version>1.5.1version>
        dependency>

2.2、删除javax相关包

2.2.1、删除javax.annotation重命名为jakarta.annotation

比如代码中使用了,代码中用到了 javax.annotation.* 下的包,目前就找不到了。

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

@Component
public class RedisLimit {
    private  DefaultRedisScript<String> redisLUAScript ;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @PostConstruct
    public void init(){
        redisLUAScript = new DefaultRedisScript<>();
        redisLUAScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limitRate.lua")));
        redisLUAScript.setResultType(String.class);
    }
}

解决

<dependency>
    <groupId>javax.annotationgroupId>
    <artifactId>javax.annotation-apiartifactId>
    <version>1.3.2version>
dependency>




<dependency>
    <groupId>jakarta.annotationgroupId>
    <artifactId>jakarta.annotation-apiartifactId>
    <version>1.3.5version>
dependency>

2.2.2、删除javax.validation重命名为jakarta.validation

--import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
--import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;

@Data
@Accessors(chain = true)
public class ApptChildRelationshipVo implements Serializable {
    private static final long serialVersionUID = 1L;
    @NotNull(message = "被监护人ID不能为空")
    private Long childId;
    /**
     * 01本人、02户主、10配偶、51父亲、52母亲、55岳父、56岳母、97其他亲属、98非亲属等等
     */
    @NotBlank(message = "监护关系不能为空")
    private String relationType;
}

<dependency>
    <groupId>jakarta.validationgroupId>
    <artifactId>jakarta.validation-apiartifactId>
    <version>3.0.2version>
dependency>

2.4、删除sun.misc.* 下的包,如sun.misc.BASE64Encoder

1、比如 sun.misc.BASE64Encoder,替换掉这个类。

        //jdk11被删除的库-encoder
        sun.misc.BASE64Encoder base64Encoder = new BASE64Encoder();
        base64Encoder.encode("xxxx");

        //使用java.util.Base64中的提供的新库替换
        java.util.Base64.Encoder encoder = Base64.getEncoder();
        encoder.encode("xxxx".getBytes());
        
        //--------------------------------------
        //jdk11被删除的库-decoder
        sun.misc.BASE64Decoder decoder = new BASE64Decoder();
        byte[] buf = decoder.decodeBuffer(dataStr);
        
        //使用java.util.Base64中的提供的新库替换	
	    Base64.Decoder decoder = Base64.getDecoder();
        byte[] buf = decoder.decode(data);
        System.out.println(new String(buf));

2、netty 低版本使用了 sun.misc.*,编译错误信息如下
升级最新版本即可解决

 Caused by: java.lang.NoClassDefFoundError: Could not initialize class io.netty.util.internal.PlatformDependent0
        at io.netty.util.internal.PlatformDependent.getSystemClassLoader(PlatformDependent.java:694) ~[netty-all-4.0.42.Final.jar!/:4.0.42.Final]

3、 lombok 使用了 com.sun.tools.javac.* 下的包,升级到最新版本即可。

三、字体相关报错

项目中使用验证码、图片、excel升级到jdk17报错

java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11FontManager

Exception java.lang.UnsatisfiedLinkError: /usr/local/jdk-17.0.9+9/lib/libfontmanager.so: libfreetype.so.6: cannot open shared object file: No such file or directory
  1. Centos上解决:sudo yum install freetype-devel -y
  2. jvm设置设置无头模式 :JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true"

四、jvm参数修改

开发可以使用高版本的JDK,字节码级别设置为低版本,就可以避免使用高版本被删除的API。低版本代码(只要是高版本未删除)都可以在高版本直接运行,兼容性可控。
jdk8升级JDK17避坑指南(适用于SpringBoot2.3---SpringBoot2.7升级)_第2张图片
适配jdk8、jdk17的的springCloud启动脚本

#!/bin/bash
APP_NAME=nuc-esquery.jar
PROFILE=dev #dev、test、prod、vfic等等
JVM_MODE=prod #test、prod ,启用不同的JVM配置
PID=$(ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}')


# 示例目录:`cd $(dirname .)/..; pwd`、`cd $(dirname .)/.; pwd`、`cd $(dirname $0)/..; pwd`
export BASE_DIR=`cd $(dirname .)/.; pwd`
export CUSTOM_SEARCH_LOCATIONS=file:${BASE_DIR}/conf/

#堆最大内存、最小内存、年轻代堆内存、
#老年代不用调,一般会是年轻代的2倍
#元空间一般也不用调整,元空间独立jvm内存,直接在物理内存上, 会自动增加
#输出:oom时候的,堆内存信息,可能:年轻代oom、元空间oom、老年代oom
#输出:gc时候的,gc日志
## 参数:-D:使用 Property形式获取;--:使用Spring里面通用的获取方式
#===========================================================================================
# JVM Configuration
#===========================================================================================
#设置测试、生产环境中,不同的JVM内存配置
if [[ "${JVM_MODE}" == "test" ]]; then
    JAVA_OPTS="-Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=640m "
    JAVA_OPTS="${JAVA_OPTS} -Dmyserver.mode=test "
    JAVA_OPTS="${JAVA_OPTS} -Dmyserver.name=application_xyz "
else
    #JAVA_OPTS="-server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m "
    JAVA_OPTS="-server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m "
    #JAVA_OPTS="-server -Xms8g -Xmx8g -Xmn4g -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m "
    JAVA_OPTS="${JAVA_OPTS} -Dmyserver.mode=prod "
    JAVA_OPTS="${JAVA_OPTS} -Dmyserver.name=application_xyz "
fi

#设置JDK8、JDK9+不同JDK版本的JVM垃圾回收器的参数
## jdk1.8返回:1;jdk17返回:17
JAVA_MAJOR_VERSION=$(java -version 2>&1 | sed -E -n 's/.* version "([0-9]*).*$/\1/p')
if [[ "$JAVA_MAJOR_VERSION" -ge "9" ]] ; then
  	JAVA_OPTS="${JAVA_OPTS} -Xlog:gc*:file=gc_trace.log:time,tags:filecount=10,filesize=102400 "
  	#解决:Could not initialize class sun.awt.X11FontManage,设置无头模式 
	JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true"
else
  	JAVA_OPTS="${JAVA_OPTS} -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+ParallelRefProcEnabled -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=java_heapdump.hprof -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc_trace.log -verbose:gc -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M "
fi

# aliyun arms
## https://arms.console.aliyun.com/#/tracing/agentList/cn-hangzhou?tab=java
#ARMS_JVM="-javaagent:/root/arms/ArmsAgent/arms-bootstrap-1.7.0-SNAPSHOT.jar -Darms.licenseKey=1426648921313446@917412bed1df111 -Darms.appName=hesuan_app146 "
ARMS_JVM=" "

# --logging.config:自定义使用外置的logback,方便用户修改,不用重新打包
# --spring.config.additional-location:可以覆盖默认的配置,覆盖默认的加载顺序和优先级。application.properties.example
#JAVA_OPTS="${JAVA_OPTS} --logging.config=./conf/project-logback.xml"
#JAVA_OPTS="${JAVA_OPTS} --spring.config.additional-location=${CUSTOM_SEARCH_LOCATIONS}"

if [ "$PID" != "" ]; then
        echo '================>停止服务..........'
        kill -9 $PID
        sleep 3s
fi

echo '================>nohup.out日志清空成功.........'
echo '' > nohup.out
echo '================>开始重启服务.........'

#启动参数
### 获取自定义启动参数的方式
### @Value("${ghouse.datacenterId:0}")
### private long datacenterId;
### 项目经常部署,开发、测试、生产、生产备用等多套环境,nacos相关会经常变动
START_ARGS="
 --spring.profiles.active=${PROFILE}\
 --custom=127.0.0.1:8850\
 --evn=my-test-key\
 --ghouse.datacenterId=1\
 --spring.cloud.nacos.discovery.server-addr=10.16.58.146:8850\
 --spring.cloud.nacos.config.server-addr=10.16.58.146:8850\
 --spring.cloud.inetutils.preferred-networks=10.16\
 --spring.cloud.nacos.config.namespace=xahs-dev\
 --spring.cloud.nacos.discovery.namespace=xahs-dev
"

echo ''
java -version
echo ''

## 常规启动版本
## java [options] -jar  [args...]
#nohup java $JAVA_OPTS -jar vaccine-epi.jar --spring.profiles.active=prod >> nohup.out 2>&1 &
#nohup java $JAVA_OPTS -jar $APP_NAME 2>&1 &
echo "java $ARMS_JVM $JAVA_OPTS -jar $APP_NAME $START_ARGS >> nohup.out 2>&1 &"
nohup java $ARMS_JVM $JAVA_OPTS -jar $APP_NAME $START_ARGS >> nohup.out 2>&1 &

tail -f nohup.out

五、jdk21运行打包java.lang.NoSuchFieldError:com.sun.tools.javac.tree.JCTree$JCImport

运行打包报错:java.lang.NoSuchFieldError:com.sun.tools.javac.tree.JCTree$JCImport

参考:Java|IDEA 运行和打包报错解决

## 升级lombok到1.18.30
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
</dependency>

你可能感兴趣的:(java,jdk17)