Java 11升级到Java 17及Spring Boot 3迁移实战指南
2022年11月,Spring Boot 3 (opens new window) 正式发布,这标志着Java生态的一个重要里程碑。新版本将最低JDK要求提升到了Java 17,这个决定并不是偶然的。
作为Java企业开发的事实标准,Spring Boot的技术选型往往引领着整个行业的走向。随着Spring Boot 3的推出,可以预见Java 17将在企业应用中快速普及。
如果你的项目还停留在Java 11,现在是时候考虑升级了。本文将分享我在实际项目中从Java 11迁移到Java 17,以及将Spring Boot 2.x升级到3.x的完整经验,包括踩过的坑和实用的解决方案。
# 为什么要升级到Java 17
说实话,很多团队对于JDK升级都比较保守,毕竟"能跑就别动"是很多项目的座右铭。但Java 17确实带来了实实在在的好处:
# 性能提升明显
根据OptaPlanner的基准测试 (opens new window),Java 17相比Java 11性能提升了8.66%到11.24%。在我们的实际项目中,升级后API响应时间平均降低了15%,GC暂停时间减少了20%。
# 安全性增强
Java 17引入了增强的伪随机数生成器,提供了更安全的加密算法支持。特别是对于金融、支付等对安全要求较高的系统,这是一个重要的升级理由。
# 开发效率提升
新特性让代码更简洁了。record
类减少了样板代码,switch
表达式让逻辑更清晰,文本块让多行字符串不再痛苦。详细的新特性可以参考Java历代版本新特性 12-17部分。
# 生态系统支持
这可能是最重要的原因。Spring Boot 3 (opens new window)、GraalVM Native Image (opens new window)等主流框架都已经拥抱Java 17。如果不升级,你将无法使用这些新版本带来的特性和优化。
# Java 11升级到Java 17实战
# 移除和废弃的功能
升级前先要搞清楚哪些东西不能用了,避免升级后才发现问题:
# Nashorn JavaScript引擎
Java 15彻底移除了Nashorn。如果你的项目中有动态执行JavaScript的需求(比如规则引擎),有两个解决方案:
<!-- 方案1:继续使用Nashorn -->
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
<!-- 方案2:迁移到GraalVM JavaScript引擎 -->
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>22.3.0</version>
</dependency>
# 实验性AOT/JIT编译器
根据JEP 410 (opens new window),Java 17移除了实验性的AOT和Graal JIT编译器。不过别担心,如果需要原生编译,可以使用独立的GraalVM发行版。
# 值得关注的新特性
这些新特性不仅仅是语法糖,合理使用能显著提升代码质量:
# Switch表达式 (opens new window)
告别繁琐的break
,代码更简洁:
// 以前
String result;
switch (day) {
case MONDAY:
case FRIDAY:
result = "工作日";
break;
case SATURDAY:
case SUNDAY:
result = "周末";
break;
default:
result = "其他";
}
// 现在
String result = switch (day) {
case MONDAY, FRIDAY -> "工作日";
case SATURDAY, SUNDAY -> "周末";
default -> "其他";
};
# ZGC垃圾收集器 (opens new window)
如果你的应用对延迟敏感,ZGC是个好选择。我们在生产环境实测,GC暂停时间稳定在10ms以内。
# 文本块 (opens new window)
写SQL或JSON终于不用拼接字符串了:
String json = """
{
"name": "张三",
"age": 25,
"address": "北京市"
}
""";
# Pattern Matching for instanceof (opens new window)
类型检查和转换一步到位:
// 以前
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// 现在
if (obj instanceof String s) {
System.out.println(s.length());
}
# Record类 (opens new window)
数据传输对象(DTO)的最佳选择:
public record UserDTO(Long id, String name, Integer age) {}
// 自动生成构造函数、getter、equals、hashCode、toString
# 密封类 (opens new window)
更精确的继承控制,特别适合领域建模:
public sealed class Shape permits Circle, Rectangle, Square {}
# 隐藏类 (opens new window)
主要用于框架开发,运行时动态生成的类可以避免内存泄漏。
# Spring Boot 2.x升级到Spring Boot 3实战
Spring Boot 3的升级比想象中要复杂,官方的迁移指南 (opens new window)是必读的,但实际操作中还有很多细节需要注意。
下面是我总结的升级要点和实战经验:
# 1. 核心依赖升级
# Spring全家桶升级
- Spring Framework 6 (opens new window):响应式编程支持更完善
- Spring Security 6 (opens new window):配置方式有较大变化,特别是
WebSecurityConfigurerAdapter
被废弃
# Jakarta EE包名大迁移
这是最让人头疼的部分。从Jakarta EE 9开始,所有javax
包名都改成了jakarta
。Spring Boot 3使用的是Jakarta EE 10,意味着你需要全局替换包名。
可以使用IDE的全局替换功能,以下是需要替换的包名清单:
原包名 | 新包名 | 常见使用场景 |
---|---|---|
javax.servlet | jakarta.servlet | Servlet、Filter、HttpServletRequest |
javax.persistence | jakarta.persistence | JPA注解(@Entity、@Table等) |
javax.validation | jakarta.validation | Bean Validation(@NotNull、@Size等) |
javax.annotation | jakarta.annotation | @Resource、@PostConstruct |
javax.transaction | jakarta.transaction | @Transactional(JTA) |
javax.websocket | jakarta.websocket | WebSocket相关 |
javax.mail | jakarta.mail | 邮件发送 |
javax.xml.bind | jakarta.xml.bind | JAXB(XML处理) |
批量替换技巧:
# 使用sed批量替换(Linux/Mac)
find . -name "*.java" -exec sed -i 's/javax\.servlet/jakarta.servlet/g' {} +
find . -name "*.java" -exec sed -i 's/javax\.persistence/jakarta.persistence/g' {} +
# 其他包名类似处理
相关的Maven依赖也需要更新,可以在Maven Repository (opens new window)查找对应的Jakarta版本。
# 2. 配置属性迁移
Spring Boot 3重命名或删除了很多配置属性,这可能导致应用启动失败或行为异常。
# 自动迁移工具
Spring Boot提供了一个迁移助手,强烈建议先用它扫描一遍:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
启动应用后,控制台会输出类似这样的提示:
Property 'spring.redis.host' is deprecated:
Reason: Use 'spring.data.redis.host' instead
Replacement: spring.data.redis.host
注意:迁移完成后记得删除这个依赖,它会影响启动性能。
# 常见配置变更
一些容易踩坑的配置变化:
spring.redis.*
→spring.data.redis.*
spring.data.mongodb.host/port
→spring.data.mongodb.uri
spring.flyway.enabled
→spring.flyway.enabled
(虽然名字没变,但默认值从true改为false了)
完整的变更列表可以查看官方配置变更日志 (opens new window)。
# 3. 自动配置机制变更
如果你开发过Spring Boot Starter,这个变化必须注意。
# 迁移方式
Spring Boot 2.x的方式(已废弃):
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration,\
com.example.AnotherAutoConfiguration
Spring Boot 3的方式:
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.MyAutoConfiguration
com.example.AnotherAutoConfiguration
注意新文件的特点:
- 文件路径多了
spring
目录 - 每行一个配置类,不需要反斜杠续行
- 文件名从
spring.factories
变成了AutoConfiguration.imports
这个改动会影响所有自定义的Starter,记得检查你的项目和依赖的第三方Starter。
# 4. URL尾部斜杠处理变化
这个变化可能会破坏现有的API兼容性,需要特别注意。
# 问题说明
Spring Boot 2.x默认会忽略URL尾部的斜杠,也就是说:
/api/users
和/api/users/
被视为相同
Spring Boot 3改变了这个行为:
/api/users
和/api/users/
被视为不同的路径- 默认只匹配精确的路径
# 解决方案
如果你的前端或客户端依赖旧的行为,可以通过配置恢复:
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// 恢复尾部斜杠匹配
configurer.setUseTrailingSlashMatch(true);
}
}
但更推荐的做法是:
- 统一API规范,明确是否使用尾部斜杠
- 使用网关或反向代理统一处理
- 在前端统一处理URL格式
# 5. Elasticsearch客户端变更
Elasticsearch 8.x废弃了High-level REST Client,Spring Boot 3也随之移除了支持。
# 迁移选择
选择1:迁移到新的Java API Client(推荐)
新客户端使用了更现代的API设计:
// 旧的方式
@Autowired
private RestHighLevelClient client;
SearchRequest searchRequest = new SearchRequest("products");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("name", "手机"));
searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 新的方式
@Autowired
private ElasticsearchClient client;
SearchResponse<Product> response = client.search(s -> s
.index("products")
.query(q -> q
.match(t -> t
.field("name")
.query("手机")
)
),
Product.class
);
具体迁移步骤请参考官方迁移指南 (opens new window)。
选择2:临时保留RestHighLevelClient
如果时间紧迫,可以手动配置来继续使用旧客户端:
@Configuration
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "uris")
public class RestHighLevelClientConfig {
@Bean
@ConditionalOnMissingBean(RestHighLevelClient.class)
public RestHighLevelClient restHighLevelClient(ElasticsearchProperties properties) {
// 解析ES节点地址
HttpHost[] hosts = properties.getUris().stream()
.map(HttpHost::create)
.toArray(HttpHost[]::new);
RestClientBuilder builder = RestClient.builder(hosts);
// 配置认证信息
if (StringUtils.hasText(properties.getUsername())) {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials(
properties.getUsername(),
properties.getPassword()
)
);
builder.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
);
}
return new RestHighLevelClient(builder);
}
}
但要注意,这只是权宜之计,最终还是要迁移到新的客户端。
# dubbo框架的升级
dubbo 3.2 (opens new window) 开始对Spring Boot 3进行支持,所以需要升级dubbo版本。
参考 2.x 升级至 3.x (opens new window)。
# jasypt-spring-boot的升级
需要注意的是,jasypt-spring-boot 3.x 基于密码的默认配置 (opens new window)发生了变化。
为了与2.x版本的兼容性,你可以手动注入jasyptStringEncryptor
,且属性与原先一致,并把org.jasypt.salt.NoOpIVGenerator
改为org.jasypt.iv.NoIvGenerator
。
@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor(@Value("${jasypt.encryptor.password}") String password) {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(password);
config.setAlgorithm("PBEWithMD5AndDES");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
# sentinel的升级
主要是适配duboo 3:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo3-adapter</artifactId>
<version>x.y.z</version>
</dependency>
对于Spring Boot 3,sentinel官网还没有发布对应的版本,你可以fork官方项目 (opens new window),把servlet迁移到Jakarta EE包名,然后发布到自己的私服。
其实对于一些三方包,可能官方还没有发布对应的Spring Boot 3版本支持,都可以暂时采用上面的方式。 比如 mqtt-spring-boot-starter (opens new window)。
# Springfox迁移到Spring Doc 2
Springfox已经不再维护,可以使用Spring Doc 2 (opens new window)代替它。
迁移文档:Migrating from SpringFox (opens new window)
# Thymeleaf模板引擎的升级
Spring Boot 3将Thymeleaf的依赖升级到3.1,参考 Thymeleaf 3.1迁移指南 (opens new window)。
其中最重要的变化是:
不再支持通过web-API提供的表达式实用对象。
在此版本中,不再提供用于表达式的#request、#response、#session和#servletContext对象。先前版本中,这些对象可以用于在Thymeleaf模板中进行表达式计算和操作,但在3.1版本中被移除了。
所以如果你的thymeleaf模板中使用了这些对象,需要进行修改。
比如原先在页面中可以直接访问servletContext
中的属性:${#servletContext.getAttribute('ctx')}
。现在则需要从Controller中传递这些属性到模板中:
@GetMapping("/index")
public String index(HttpServletRequest request, Model model) {
model.addAttribute("ctx", request.getServletContext().getAttribute("ctx"));
return "index";
}
页面中使用ctx属性:
<link th:href="${ctx}+'/css/base.css'" rel="stylesheet" type="text/css">
# 总结
更高的性能,更好的安全性,更好的开发体验和开发效率,更好的生态支持。Java 17与Spring Boot 3你值得拥有!
祝你变得更强!