版本
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<>());
}
}
}