jdk8升级JDK17避坑指南(适用于SpringBoot2.3---SpringBoot2.7升级)
温馨提示:这篇文章已超过388天没有更新,请注意相关的内容是否还可用!
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.3、删除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肯定没问题。
总结:模块化需要增加的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) /** * 对象之间copy,可以是相同类型,也可以是不同类型;但不能是集合 * * @param srcObj * @param targetObj * @return dest */ @Bean(name = "beanMapper") public BeanMapper registUtilsBeanMapper() { //多个Bean复制实现类,使用其中一个 ///return new DozerBeanMapperImpl(); return new OrikaBeanMapperImpl(); } } /** * 实例. */ 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 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 return MAPPER.mapAsList(srcObjs, destClz); } } /** * 持有Dozer单例, 避免重复创建DozerMapper消耗资源. */ private static DozerBeanMapper dozer = new DozerBeanMapper(); /** * 基于Dozer转换对象的类型. */ public return dozer.map(source, destinationClass); } /** * 基于Dozer转换Collection中对象的类型. */ public List T destinationObject = dozer.map(sourceObject, destinationClass); destinationList.add(destinationObject); } return destinationList; } /** * 基于Dozer将对象A的值拷贝到对象B中. * src 和 dest 对象均不能为空 */ @Override public 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 return mapList(srcObjs, destClz); } } private DefaultRedisScript redisLUAScript = new DefaultRedisScript 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; } 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 org.projectlombok lombok 1.18.30

