轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

    • 并发

    • 经验

      • Java8升级到Java11的实践
      • Java 11升级到Java 17及Spring Boot 3迁移实战指南
        • 为什么要升级到Java 17
          • 性能提升明显
          • 安全性增强
          • 开发效率提升
          • 生态系统支持
        • Java 11升级到Java 17实战
          • 移除和废弃的功能
          • Nashorn JavaScript引擎
          • 实验性AOT/JIT编译器
          • 值得关注的新特性
          • Switch表达式
          • ZGC垃圾收集器
          • 文本块
          • Pattern Matching for instanceof
          • Record类
          • 密封类
          • 隐藏类
        • Spring Boot 2.x升级到Spring Boot 3实战
          • 1. 核心依赖升级
          • Spring全家桶升级
          • Jakarta EE包名大迁移
          • 2. 配置属性迁移
          • 自动迁移工具
          • 常见配置变更
          • 3. 自动配置机制变更
          • 迁移方式
          • 4. URL尾部斜杠处理变化
          • 问题说明
          • 解决方案
          • 5. Elasticsearch客户端变更
          • 迁移选择
          • dubbo框架的升级
          • jasypt-spring-boot的升级
          • sentinel的升级
          • Springfox迁移到Spring Doc 2
          • Thymeleaf模板引擎的升级
        • 总结
    • JVM

    • 企业应用

  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 经验
轩辕李
2023-07-11
目录

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);
    }
}

但更推荐的做法是:

  1. 统一API规范,明确是否使用尾部斜杠
  2. 使用网关或反向代理统一处理
  3. 在前端统一处理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你值得拥有!

祝你变得更强!

编辑 (opens new window)
#Java17#SpringBoot3#升级迁移
上次更新: 2025/08/15
Java8升级到Java11的实践
JVM体系

← Java8升级到Java11的实践 JVM体系→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式