版本

     JDK  11 -> 17
     Gradle  6.0 -> 8.2.1
     SpringBoot 2.2 -> 3.1.2

JDK升级

      下载安装JDK17并修改JAVA_HOME

Gradle升级

      下载 https://services.gradle.org/distributions/gradle-8.2.1-bin.zip

      解压 gradle-8.2.1-bin.zip 到 D:\Program Files\

      环境变量Path里的Gradle路径修改为D:\Program Files\gradle-8.2.1\bin

      进入项目目录,执行

gradle wrapper --gradle-version 8.2.1

修改 build.gradle

    修改版本号

plugins {
	id 'org.springframework.boot' version '3.1.2'
	id 'io.spring.dependency-management' version '1.1.2'
	id 'java'
}

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

compileJava {
    options.compilerArgs += ['-parameters']
}

    validation不再默认导入,需要在dependencies里加上

implementation 'org.springframework.boot:spring-boot-starter-validation'

替换JAVA代码Java EE to Jakarta EE

    import javax.全替换成import jakarta.

修改JAVA代码(个别类和方法定义变更)

    根据错误提示查看源码说明,进行响应的修改

修改配置文件

    redis相关:
          spring.redis.url              -> spring.data.redis.url
          spring.redis.password   -> spring.data.redis.password
          spring.data.redis.url里的地址需要由原来的http:// https:// 改成 redis:// rediss//

      log相关:
          logging.file                     -> logging.file.name
          Logback 和 Log4j2 的日志消息的日期和时间部分的默认格式已经改变,以符合ISO-8601标准。新的默认格式 yyyy-MM-dd'T'HH:mm:ss.SSSXXX 使用T来分隔日期和时间,而不是空格字符,并在最后添加时区偏移。LOG_DATEFORMAT_PATTERN 环境变量或 logging.pattern.dateformat 属性可以用来恢复以前的默认值yyyy-MM-dd HH:mm:ss.SSS。

      jackson相关:
          json日期输出格式里时区的表示发生了变更,升级前格式2023-08-07T10:21:50.138+0000,升级后格式2023-08-07T10:29:43.321+00:00,如果需要保持之前的格式,可以加入配置项目
         spring.jackson.date-format=yyyy-MM-dd'T'hh:mm:ss.SSSZ

      其他配置项目按警告信息和IDE提示进行响应的修改

关于路径匹配

     升级以前 /foo/bar 等同于 /foo/bar/
     升级以后 /foo/bar 不同于 /foo/bar/,/foo/bar/会报找不到路径

     如果需要保留识别/foo/bar/可以新加一个配置类,注意:setUseTrailingSlashMatch()方法已经标记为Deprecated,后续版本可能被删除。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
      configurer.setUseTrailingSlashMatch(true);
    }
}

关于是否使用Session

      官方的说明:

  • .     在SpringSecurity5中,默认行为是使用SecurityContextPersistenceFilter将SecurityContext自动保存到SecurityContextRepository。保存必须在提交HttpServlet响应之前和SecurityContextPersistenceFilter之前完成。
  •       在SpringSecurity6中,默认行为是SecurityContextHolderFilter将只从SecurityContextRepository读取SecurityContext并将其填充到SecurityContextHolder中。如果用户希望SecurityContext在请求之间持久存在,则现在必须使用SecurityContextRepository显式保存SecurityContext。

     这次升级的系统使用的是Basic HTTP Authentication,恰巧属于受影响的范围,其使用的BasicAuthenticationFilter在认证成功后会调用SecurityContextRepository的saveContext方法,但是默认使用的NullSecurityContextRepository/RequestAttributeSecurityContextRepository都不会开启Session。可以使用下面的方式使认证成功后自动写入Session。

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        // ...
        .httpBasic((basic) -> basic
            .addObjectPostProcessor(new ObjectPostProcessor<BasicAuthenticationFilter>() {
                @Override
                public <O extends BasicAuthenticationFilter> O postProcess(O filter) {
                    filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
                    return filter;
                }
            })
        );

    return http.build();
}

     上述内容也适用于其他认证机制,如Bearer Token Authentication。基于Form的用户名密码认证貌似没有这个问题,因为会使用UsernamePasswordAuthenticationFilter,其父类AbstractAuthenticationProcessingFilter里会调用SessionAuthenticationStrategy的onAuthentication进行认证后处理。

     另外,如果对SecurityContext里的内容进行修改后,需要手动调用SecurityContextRepository.saveContext()保存到Session中才能持久化。

SecurityContextHolder.setContext(securityContext);
SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);

关于SpringSession的配置

    升级前有spring.session.store-type的配置项目,可以通过修改项目值为none/redis来使用内存模式或者redis保存Session内容。
    升级后已经没有该项目,使用自动配置模式的时候,会依次查找有没有导入下面的项目,如果有就使用第一个找到的方式进行Session内容的持久化。
           spring-session-data-redis
           spring-session-jdbc
           spring-session-hazelcast
           spring-session-data-mongodb

    也可以不使用自动配置,使用注解的方式启用对应方式的持久化,比如:@EnableRedisHttpSession/@EnableRedisIndexedHttpSession。注意:升级之前@EnableRedisHttpSession使用的是RedisIndexedSessionRepository,升级之后使用的是RedisSessionRepository,如果想使用RedisIndexedSessionRepository,请使用@EnableRedisIndexedHttpSession

   下面的配置类实现了通过配置项目切换是否使用redis对session进行持久化:

@Configuration
public class HttpSessionConfig {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(name="spring.session.store-type", havingValue = "redis")
    @EnableRedisIndexedHttpSession
    static class RedisHttpSessionConfiguration {
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(name="spring.session.store-type", havingValue = "redis")
    @AutoConfigureAfter(RedisIndexedHttpSessionConfiguration.class)
    @EnableConfigurationProperties(RedisSessionProperties.class)
    static class AfterRedisHttpSessionConfiguration {

        public AfterRedisHttpSessionConfiguration(RedisIndexedSessionRepository sessionRepository, RedisSessionProperties redisSessionProperties) {
            sessionRepository.setFlushMode(redisSessionProperties.getFlushMode());
            sessionRepository.setRedisKeyNamespace(redisSessionProperties.getNamespace());
        }
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(name="spring.session.store-type", havingValue = "none", matchIfMissing = true)
    @EnableSpringHttpSession
    static class MapHttpSessionConfiguration {
        @Bean
        public MapSessionRepository sessionRepository() {
            return new MapSessionRepository(new ConcurrentHashMap<>());
        }
    }
}