jdk17和springboot3.x升级笔记

1,822 阅读9分钟

背景

老的项目使用jdk1.8,spring5.x,使用外置tomcat部署。由于低版本存在安全隐患,需要把老项目升级为jdk17,springboot3.x,外置tomcat改为内置tomcat启动。

老版本spring漏洞

  • 特殊匹配模式下身份认证绕过漏洞(CVE-2023-20860)
  • spring mvc 反射型文件下载漏洞
  • spring web uriComponentsBuilder url解析不当漏洞

image.png

升级思路

  • 修改pom引用,jdk8->17,spring 5.x->springboot3.x
  • 排除第三方包中spring的低版本引用
  • 对项目进行编译,启动,依次对不兼容组件进行升级
  • 解决jar报冲突
  • 配置内置tomcat 启动参数,需要调优

涉及相关组件升级,相关改动如下

javax.servlet->jakarta.servlet

2018年,Java EE更名为Jakarta EE,spring 6.x后使用jdk17,并且对javax.servlet改为jakarta.servlet,升级为springboot3.x后,javax.servlet需要全部替换

servlet相关类包名替换,javax.servlet->jakarta.servlet

相关annotaion包名替换,javax.annotation->jakarta.annotation

JSR-330的@Inject、JSR 250的@PostConstruct、@Predestroy以及及其常用的@Resource注解,Resource包名变成了jakarta.annotation.Resource。

需要注意的是,如果第三方包中有使用javax.annotation相关注解,可能不再生效.

spring高版本本身有做javax.annotation相关的兼容 如javax.annotation.PostConstruct注解 spring版本6.1.10,源码如下

CommonAnnotationBeanPostProcessor

public CommonAnnotationBeanPostProcessor() {
    setOrder(Ordered.LOWEST_PRECEDENCE - 3);

    // Jakarta EE 9 set of annotations in jakarta.annotation package
    addInitAnnotationType(loadAnnotationType("jakarta.annotation.PostConstruct"));
    addDestroyAnnotationType(loadAnnotationType("jakarta.annotation.PreDestroy"));

    // Tolerate legacy JSR-250 annotations in javax.annotation package
    addInitAnnotationType(loadAnnotationType("javax.annotation.PostConstruct"));
    addDestroyAnnotationType(loadAnnotationType("javax.annotation.PreDestroy"));

    // java.naming module present on JDK 9+?
    if (jndiPresent) {
       this.jndiFactory = new SimpleJndiBeanFactory();
    }
}

//获取注解时如果类路径未引用,返回的null
@Nullable
private static Class<? extends Annotation> loadAnnotationType(String name) {
    try {
       return (Class<? extends Annotation>)
             ClassUtils.forName(name, CommonAnnotationBeanPostProcessor.class.getClassLoader());
    }
    catch (ClassNotFoundException ex) {
       return null;
    }
}

所以,如果仍需要使用javax.annotation注解,需要引用该包

<!--兼容老的javax.annotation注解-->
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

第三方包中如果使用servlet,如果能反编译,则反编译后重复做上面两个步骤;如果不能反编译,看下是否有高版本支持;如果既不能反编译又没有高版本支持,比如hystrix,可以寻找替换方案

hystrix 替换为 resilience4j,需要注意老的线程池等参数配置,尽量保持一致。改完之后需要对新的组件进行压测 参考 io.dropwizard.metrics 替换为 springboot-actuator 参考

兼容老的spring配置

为了尽量少的修改代码,对老的xml,properties配置做兼容处理

properties文件加载

方式一,使用@PropertySource注解,这种方式需要把每一个property文件做配置
@PropertySource({"classpath:applicationConfigurations/allowedHeaders.properties",
        "classpath:applicationConfigurations/couchbase.properties"})
@Configuration
public class MyConfigs{

}

方式二,手动读取配置加载,这种方式好处是如果配置很多,不用一个个的配置,可以直接读取加载相对路径下所有的配置
public class PropertySourcesInit implements ApplicationContextInitializer<AbstractApplicationContext> {
    @Override
    public void initialize(AbstractApplicationContext ctx) {
        ConfigurableEnvironment environment = ctx.getEnvironment();
        MutablePropertySources propertySources = environment.getPropertySources();
        URL defaultPropertiesFolder = this.getClass().getClassLoader().getResource("properties 文件相对路径");
        PropertiesFolderPropertySource aggProps = new PropertiesFolderPropertySource("internal files: "
                + DEFAULT_PROP_PATH, defaultPropertiesFolder);
        propertySources.addLast(propertySource);
    }
    
然后在application.properties文件中指定context初始化

context.initializer.classes=com.xxx.PropertySourcesInit

加载xml配置

通过ImportResource注解加载老的xml配置,该注解支持通配符

@PropertySource({"classpath:applicationConfigurations/allowedHeaders.properties",
        "classpath:applicationConfigurations/couchbase.properties"})
@ImportResource({"classpath:Application*.xml","classpath:*Context.xml","classpath:*Source.xml","classpath:*context.xml","classpath:*aop*.xml"})
@Configuration
public class MyConfigs{

}

一些报错及解决方案

springboot本身有很多auto config,如果报错重复加载,增加以下配置

The bean 'serviceProperties', defined in class path resource [ApplicationConfigurationContext.xml], could not be registered. A bean with that name has already been defined in file [/xxx/ApplicationConfigurationContext.xml] and overriding is disabled.

application.properties中增加下面配置,允许重写
spring.main.allow-bean-definition-overriding= true

如果循环依赖,可以增加以下配置

报错信息:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'xxxService': Requested bean is currently in creation: Is there an unresolvable circular reference?
The dependencies of some of the beans in the application context form a cycle:

application.properties中增加下面配置,允许
spring.main.allow-circular-references= true

web.xml处理

老的war包部署到tomcat中,依赖web.xml配置,改为springboot内置tomcat部署后,需要把web.xml中内容迁移到配置类中 比如老的web.xml中配置的Filter

<filter>
    <filter-name>staticFilter</filter-name>
    <filter-class>com.xxx.StaticFilter</filter-class>
</filter>

改为配置类

@Configuration
@ImportResource({"classpath:*Context.xml", "classpath:*Source.xml", "classpath:*context.xml", "classpath:*aop*.xml"})
public class CustomerConfigs {

    @Bean
    public FilterRegistrationBean<Filter> getStaticFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new StaticFilter());
        filterRegistrationBean.addUrlPatterns("/docs/*");
        filterRegistrationBean.setName("staticFilter");
        return filterRegistrationBean;
    }

}

迁移时需要注意Filter的顺序,web.xml中执行顺序为 配置顺序,改为config bean之后,需要手动设置order

org.springframework.data.repository.CrudRepository兼容处理

新旧版本,CrudRepository 方法有些不同,根据新版本的做兼容处理即可

org.springframework.beans.factory.annotation.Required

org.springframework.beans.factory.annotation.Required在spring高版本中已删除
从Spring 5.1开始,@Required 注解已经被弃用,并在未来版本中可能会被移除。如果你使用的是Spring 5.1或更高版本,应该使用 @Autowired 注解来标记依赖必须在构造函数、工厂方法或字段注入时提供。

如果你需要保持对Setter注入的强制性,可以使用 @RequiredArgsConstructor@NonNull 注解,这需要Lombok库的支持。

redis

使用springboot-data自动加载

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>

如果没使用到jpa,排除jpa自动加载

报错信息:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': JPA metamodel must not be empty
Caused by: java.lang.IllegalArgumentException: JPA metamodel must not be empty

启动类排除
exclude = {
        JpaRepositoriesAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class
}

jedis升级为高版本

报错信息:

Caused by: java.lang.ClassNotFoundException: redis.clients.jedis.JedisClientConfig


<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.8.1</version>
</dependency>
        
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>5.1.0</version>
  </dependency>
  
  
报错信息:

Caused by: java.lang.ClassNotFoundException: redis.clients.jedis.Queable


jedis版本不匹配,改为下面版本
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.2</version>
</dependency>

EhCache

EhCache升级为3.10.8

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.10.8</version>
</dependency>

报错信息:

Caused by: java.lang.ClassNotFoundException: org.springframework.cache.ehcache.EhCacheManagerFactoryBean

springboot 3.x之后使用JcacheManager适配cache 1,删除xml配置

<bean id="ehcache"
        class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache.xml" />
        <property name="shared" value="true" />
    </bean>

2,使用@EnableCaching开启缓存

3,通过application.properties指定ehcache.xml路径

spring.cache.jcache.config=classpath:ehcache.xml

4,ehcache.xml配置为高版本配置,需要注意新老配置是否一致!!!

旧的配置
<cache name="cacheNames"
    maxEntriesLocalHeap="1000"
    eternal="false"
    timeToLiveSeconds="1800"
    memoryStoreEvictionPolicy="LRU"
    statistics="true">
    <persistence strategy="localTempSwap" />
</cache>

新的配置
<eh:cache-template name="default">
    <eh:expiry>
        <!-- 过期策略 ttl:time to live,指定条目的存活时间 -->
        <eh:ttl unit="seconds">1800</eh:ttl>

        <!-- 过期策略 tti:time to idle,条目在指定时间段内未被使用,则过期 -->
        <!-- <eh:tti unit="seconds">600</eh:tti>-->

        <!-- 不过期 只能手动删除 -->
        <!--<eh:none/>-->
    </eh:expiry>
    <eh:resources>
        <!--堆内内存可以的条目-->
        <eh:heap unit="entries">1000</eh:heap>

        <!-- 超过条目,保存到堆外内存 不配表示关闭-->
        <!--<eh:offheap unit="MB">10</eh:offheap>-->

        <!-- 堆外内存超了之后 保存至磁盘 -->
        <!--<eh:disk unit="MB">10</eh:disk>-->

        <!--缓存顺序-->
    </eh:resources>
</eh:cache-template>

<!-- 继承自defalt的配置 -->
<eh:cache alias="cacheNames" uses-template="default">

</eh:cache>


CXFServlet

apache cxf改为jersey 1,删除web.xml中配置

 <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet>
        <servlet-name>CXFServlet</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping> 

2,pom文件引入jersey

<!--jersey-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jersey</artifactId>
    <version>${spring.boot.version}</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-common</artifactId>
    <version>3.1.3</version>
</dependency>

3,配置jersey

@Component
public class JerseyConfig extends ResourceConfig {


    public JerseyConfig() {

        //配置扫描包
        packages("com.xxx.rest");
        //注册Mapper
        register(RetrofitErrorMapper.class);
        //注册Provider
        register(new JacksonJsonProvider(objectMapper));
        //注册Filter
        register(getFilteredLoggingInInterceptor());
    }
    

    private FilteredLoggingInInterceptor getFilteredLoggingInInterceptor(){
        FilteredLoggingInInterceptor filter = new FilteredLoggingInInterceptor();
        filter.setPrettyLogging(true);
        filter.setShowBinaryContent(true);
        filter.setFilterPatterns(List.of("/swagger.json","/admin/","/health/"));
        filter.setOutputJSON(true);
        return filter;
    }


}

jooq

jooq-codegen-maven从3.4.4升级到3.17.16版本,兼容jakarta.servlet jooq官方文档 www.jooq.org/doc/3.17/ma…

1,修改配置文件

原有配置文件

<plugin>-->
<!--                <groupId>org.jooq</groupId>-->
<!--                <artifactId>jooq-codegen-maven</artifactId>-->
<!--                <version>3.4.4</version>-->
<!--                <executions>-->
<!--                    &lt;!&ndash; Generate the required class from the database &ndash;&gt;-->
<!--                    <execution>-->
<!--                        <id>generate-h2</id>-->
<!--                        <phase>generate-sources</phase>-->
<!--                        <goals>-->
<!--                            <goal>generate</goal>-->
<!--                        </goals>-->
<!--                    </execution>-->
<!--                </executions>-->
<!--                <dependencies>-->
<!--                    <dependency>-->
<!--                        <groupId>com.h2database</groupId>-->
<!--                        <artifactId>h2</artifactId>-->
<!--                        <version>${h2.version}</version>-->
<!--                    </dependency>-->
<!--                </dependencies>-->

<!--                <configuration>-->
<!--                    <jdbc>-->
<!--                        <driver>${db.driver}</driver>-->
<!--                        <url>${db.url}</url>-->
<!--                        <user>${db.username}</user>-->
<!--                        <password>${db.password}</password>-->
<!--                    </jdbc>-->

<!--                    <generator>-->
<!--                        <database>-->
<!--                            &lt;!&ndash; Configure the used database dialect &ndash;&gt;-->
<!--                            <name>org.jooq.util.h2.H2Database</name>-->
<!--                            &lt;!&ndash; Include all tables found from the PUBLIC -->
<!--                                schema &ndash;&gt;-->
<!--                            <includes>.*</includes>-->
<!--                            <excludes />-->
<!--                            <inputSchema>PUBLIC</inputSchema>-->
<!--                            <outputSchemaToDefault>true</outputSchemaToDefault>-->
<!--                        </database>-->
<!--                        &lt;!&ndash; Generate classes for tables and records &ndash;&gt;-->
<!--                        <generate>-->
<!--                            <records>true</records>-->
<!--                        </generate>-->
<!--                        &lt;!&ndash; Configure the target package and directory &ndash;&gt;-->
<!--                        <target>-->
<!--                            <packageName>com.xxx.domain.jooq</packageName>-->
<!--                            <directory>target/generated-sources/jooq</directory>-->
<!--                        </target>-->
<!--                    </generator>-->
<!--                </configuration>-->
<!--            </plugin>-->

改后配置文件

<plugin>
                <!-- Use org.jooq                for the Open Source Edition
                         org.jooq.pro            for commercial editions with Java 17 support,
                         org.jooq.pro-java-11    for commercial editions with Java 11 support,
                         org.jooq.pro-java-8     for commercial editions with Java 8 support,
                         org.jooq.trial          for the free trial edition with Java 17 support,
                         org.jooq.trial-java-11  for the free trial edition with Java 11 support,
                         org.jooq.trial-java-8   for the free trial edition with Java 8 support

                     Note: Only the Open Source Edition is hosted on Maven Central.
                           Install the others locally using the provided scripts, or access them from here: https://repo.jooq.org -->
                <groupId>org.jooq</groupId>
                <artifactId>jooq-codegen-maven</artifactId>
                <version>3.17.16</version>

                <!-- The jOOQ code generation plugin is also executed in the generate-sources phase, prior to compilation -->
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>

                <dependencies>
                    <dependency>
                        <groupId>com.h2database</groupId>
                        <artifactId>h2</artifactId>
                        <version>${h2.version}</version>
                    </dependency>
                </dependencies>

                <!-- This is a minimal working configuration. See the manual's section about the code generator for more details -->
                <configuration>
                    <jdbc>
                        <driver>${db.driver}</driver>
                        <url>${db.url}</url>
                        <user>${db.username}</user>
                        <password>${db.password}</password>
                    </jdbc>
                    <generator>
                        <database>
                            <!-- Configure the used database dialect -->
<!--                            <name>org.jooq.util.h2.H2Database</name>-->
                            <!-- Include all tables found from the PUBLIC
                                schema -->
                            <includes>.*</includes>
                            <excludes />
                            <inputSchema>PUBLIC</inputSchema>
                            <outputSchemaToDefault>true</outputSchemaToDefault>
                        </database>
                        <!-- Generate classes for tables and records -->
                        <generate>
                            <records>true</records>
                        </generate>
                        <!-- Configure the target package and directory -->
                        <target>
                            <packageName>com.xxx.domain.jooq</packageName>
                            <directory>target/generated-sources/jooq</directory>
                        </target>
                    </generator>
                </configuration>
            </plugin>

jooq升级新版本后问题

日期类型问题 报错信息

对于set(org.jooq.TableField<com.xxx.TrackerRecord,java.time.LocalDateTime>,java.sql.Timestamp), 找不到合适的方法
SQL DATE modelled by java.time.LocalDate and JDBC's java.sql.Date
SQL TIME modelled by java.time.LocalTime and JDBC's java.sql.Time
SQL TIMESTAMP modelled by java.time.LocalDateTime and JDBC's java.sql.Timestamp

解决方案: 修改业务代码,兼容新版本日期类型。原来数据库是UTC时间,接口返回的是东八区时间,需要确认jooq高版本时间是否一致 generator 时指定sql Date类型生成为TIMESTAMP类型,避免改动业务代码---这种方式未生效,使用的修改代码 www.jooq.org/xsd/jooq-co… 高版本时间类型默认为LocalDateTime Record.set()不再支持Timestamp参数,需要改成LocalDateTime Record.get()不再支持Date,需要改成LocalDateTime

rabbit-mq

报错信息:
Caused by: org.xml.sax.SAXParseException: cvc-complex-type.3.2.2: Attribute 'publisher-confirms' is not allowed to appear in element 'rabbit:connection-factory'.

老的publisher-confirms 配置方式需要改为confirm-type

kafka

Caused by: java.lang.NoClassDefFoundError: org/springframework/kafka/retrytopic/RetryTopicConfiguration


升级spring-kafka版本
从1.2.2.RELEASE升级到3.0.12
<dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>3.0.12</version>
        </dependency>

log4j

报错信息:

Correct the classpath of your application so that it contains compatible versions of the classes org.springframework.boot.logging.log4j2.Log4J2LoggingSystem and org.apache.logging.log4j.util.PropertiesUtil
Spring Boot 3.0.2 依赖于不易受到攻击的 log4j 2.19,因此您必须有一个属性将其显式设置为 2.18,否则这将成为一个问题。不需要这样的属性(正如您所发现的,这是一种回归,无论如何都会使其不兼容。)

升级log4j版本
<dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.20.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.20.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-web</artifactId>
            <version>2.20.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.20.0</version>
        </dependency>

第三方jar依赖低版本jdk

Caused by: java.lang.ClassNotFoundException: javax.xml.ws.Service

引入相关jar

<dependency>
            <groupId>javax.xml.ws</groupId>
            <artifactId>jaxws-api</artifactId>
            <version>2.3.1</version>
        </dependency>



Caused by: javax.xml.soap.SOAPException: Unable to create SAAJ meta-factory: Provider com.sun.xml.internal.messaging.saaj.soap.SAAJMetaFactoryImpl not found


引入	
<dependency>
            <groupId>com.sun.xml.messaging.saaj</groupId>
            <artifactId>saaj-impl</artifactId>
            <version>1.5.1</version>
        </dependency>
        
Caused by: java.lang.NullPointerException: Cannot invoke "javax.xml.ws.spi.Provider.createServiceDelegate(java.net.URL, javax.xml.namespace.QName, java.lang.Class)" because the return value of "javax.xml.ws.spi.Provider.provider()" is null
引入
<dependency>
            <groupId>com.sun.xml.ws</groupId>
            <artifactId>rt</artifactId>
            <version>2.3.1</version>
        </dependency>
        
        
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @5e600dd5

启动参数增加
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED

quartz


Caused by: org.quartz.SchedulerConfigException: DataSource name not set.

https://blog.csdn.net/a15835774652/article/details/135550896
在springboot 2.6 之前 集成quartz 配置 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX 来定义quartz默认的数据源支持
但是在2.6之后 不在支持此方式进行配置默认数据源 需要使用 新的类
org.springframework.scheduling.quartz.LocalDataSourceJobStore


报错及解决

servlet编码问题

客户端请求后,postman报错 Error: aborted并且无法正常接收相应结果;debug发现服务端有正常返回。 使用curl -v 发现返回有乱码,并报错curl: (18) transfer closed with 32 bytes remaining to read,判断是编码格式不对

curl -v --location 'localhost:8088/xxxx/profile/event?srcId=123' \

--header 'Content-Type: application/json' \

--header 'Accept: */*' \

--data '{

    "srcId":"123"

}'

* Connected to localhost (::1) port 8088

> POST /xxxx/profile/event?srcId=123 HTTP/1.1

> Host: localhost:8088

> User-Agent: curl/8.4.0

> Content-Type: application/json

> Accept: */*

> Content-Length: 21

> 

< HTTP/1.1 400 

< vary: Origin

< X-Conversation-Id: SYS449294038695-2183008-2015353-1760374

< X-Page-ID: 

< X-Correlation-ID: SYS449294038695-4533294-3745116-4862556

< X-CorrelationId: a30f861d-86f7-4f38-a476-a544d0302973

< X-Disney-Internal-Service-Stopwatch: 546

< X-Disney-Internal-Service-Host: localhost:8088

< Content-Type: application/json;charset=ISO-8859-1

< Content-Length: 576

< Date: Mon, 12 Aug 2024 03:40:39 GMT

< Connection: close

< 

* transfer closed with 32 bytes remaining to read

* Closing connection

curl: (18) transfer closed with 32 bytes remaining to read

{"errors":[{"errorCode":"WDPRO_3","errorType":"FIELD_VALIDATION_ERROR","errorLocation":"handleProfileEvent.context.srcSys","errorMessage":"????"},{"errorCode":"WDPRO_3","errorType":"FIELD_VALIDATION_ERROR","errorLocation":"handleProfileEvent.context.id","errorMessage":"????"},{"errorCode":"WDPRO_3","errorType":"FIELD_VALIDATION_ERROR","errorLocation":"handleProfileEvent.context.type","errorMessage":"????"},{"errorCode":"WDPRO_3","errorType":"FIELD_VALIDATION_ERROR","errorLocation":"handleProfileEvent.context.data","errorMessage":"????"}]} **%**

解决方案是,springboot 增加如下配置

server.servlet.encoding.enabled=true
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.force=true

jdk模块化

报错信息

java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.time.LocalDateTime java.time.ZonedDateTime.dateTime accessible: module java.base does not "opens java.time" to unnamed module @4de4b452

在 Java8 中,没有人能阻止你访问特定的包,比如 sun.misc,对反射也没有限制,只要 setAccessible(true) 就可以了。Java9 模块化以后,一切都变了,只能通过 --add-exports--add-opens 来打破模块封装

  • --add-opens 导出特定的包
  • --add-opens 允许模块中特定包的类路径深度反射访问
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens java.base/java.nio=ALL-UNNAMED
--add-opens java.base/java.security=ALL-UNNAMED
--add-opens java.base/java.text=ALL-UNNAMED
--add-opens java.base/java.time=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/jdk.internal.access=ALL-UNNAMED
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED

总结

升级过程中主要改动

1,开源组件间版本不兼容,需要升级版本(json,swagger等),更改配置方式(ehcache),修改方法使用方式(spring-rabbit confirm方法,jooq时间类型),包名替换(javax-》jakarta,rest相关组件都需要升级支持jakarta,jersey,swagger)

2,非开源组件升级,重写(copy改)或者动态替换(BeanPostProcessor)