文章 587
评论 5
浏览 228760
Java 21 生产迁移实战 ②:废弃 API 替换与兼容性处理清单

Java 21 生产迁移实战 ②:废弃 API 替换与兼容性处理清单

一、引言

在上一篇文章《Java 21 生产迁移实战 ①》中,我们讨论了为什么要迁移到 Java 21。

今天,我们进入实战阶段——废弃 API 的替换与兼容性处理

Java 21 中标记为废弃(Deprecated)的 API 数量不少,但真正需要立即处理的只有几类。本文将逐类列出这些必改的废弃 API,提供具体的代码替换方案,并介绍两个强大的自动化工具:Maven modernizer 插件OpenRewrite,帮助你快速完成迁移。


二、迁移准备工作

2.1 环境要求

组件版本要求
Java21
Maven3.8+
Spring Boot3.2+(如果使用)

2.2 迁移策略

迁移策略:
┌─────────────────────────────────────────────────────┐
│                                                     │
│  1. 自动扫描(modernizer)                           │
│     ↓                                               │
│  2. 批量修复(OpenRewrite)                          │
│     ↓                                               │
│  3. 手动修复(剩余问题)                              │
│     ↓                                               │
│  4. 测试验证                                         │
│     ↓                                               │
│  5. 灰度发布                                         │
│                                                     │
└─────────────────────────────────────────────────────┘

三、废弃 API 替换清单

3.1 第一类:java.security.AccessController

废弃原因:Java 17+ 引入了更简洁的安全模型,AccessController 已不再需要。

替换方案:直接调用目标方法,不再使用 AccessController.doPrivileged()。

代码对比

// ❌ 旧代码:使用 AccessController
import java.security.AccessController;
import java.security.PrivilegedAction;

String result = AccessController.doPrivileged(
    (PrivilegedAction<String>) () -> {
        // 需要权限的操作
        return System.getProperty("user.home");
    }
);

// ✅ 新代码:直接调用
String result = System.getProperty("user.home");

批量替换模式

// 模式匹配:AccessController.doPrivileged
AccessController.doPrivileged\((PrivilegedAction<[^>]+>) \(\) -> \{([^}]+)\}\)

// 替换为:直接执行 Lambda 体内容
// 提取 $2 中的代码

3.2 第二类:Thread.stop/resume/suspend

废弃原因:这些方法是不安全的,可能导致死锁、数据损坏等问题。

替换方案:使用正确的中断/取消模式。

⚠️ 重要提示:这不是用虚拟线程替代!虚拟线程是并发模型的升级,与线程停止机制无关。正确的做法是使用 Thread.interrupt()CompletableFuture 的取消机制。

代码对比

// ❌ 旧代码:使用 Thread.stop()
Thread worker = new Thread(() -> {
    while (true) {
        // 执行任务
    }
});
worker.start();

// 强行停止线程(不安全)
worker.stop();

// ✅ 新代码:使用中断机制
class Worker implements Runnable {
    private volatile boolean running = true;
    
    @Override
    public void run() {
        while (running && !Thread.currentThread().isInterrupted()) {
            try {
                // 执行任务
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 恢复中断状态
                running = false;
            }
        }
    }
    
    public void stop() {
        running = false;
    }
}

Worker worker = new Worker();
Thread thread = new Thread(worker);
thread.start();

// 安全停止
worker.stop();
thread.interrupt();

使用 ExecutorService 的方案

// ✅ 使用 ExecutorService 管理线程
ExecutorService executor = Executors.newSingleThreadExecutor();

Future<?> future = executor.submit(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 执行任务
    }
});

// 取消任务(会中断线程)
future.cancel(true);

// 关闭执行器
executor.shutdownNow();

使用 CompletableFuture 的方案

// ✅ 使用 CompletableFuture + 取消令牌
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 执行任务
    }
    return "result";
});

// 取消任务
future.cancel(true);

3.3 第三类:URL 构造函数

废弃原因:URL 构造函数不进行严格的语法检查,可能导致运行时错误。

替换方案:使用 URI.create(str).toURL()new URI(str).toURL()

代码对比

// ❌ 旧代码:使用 URL 构造函数
import java.net.URL;

// 可能抛出 MalformedURLException,但不检查所有语法错误
URL url = new URL("https://example.com/path?query=value");

// ✅ 新代码:使用 URI.toURL()
import java.net.URI;
import java.net.URL;

// URI 会进行严格的语法检查
URI uri = URI.create("https://example.com/path?query=value");
URL url = uri.toURL();

// 或者使用 try-catch
try {
    URL url2 = new URI("https://example.com/path?query=value").toURL();
} catch (Exception e) {
    // 处理异常
}

批量替换模式

// 模式匹配:new URL("...")
new URL\("([^"]+)"\)

// 替换为:URI.create("$1").toURL()
URI.create("$1").toURL()

3.4 第四类:Finalizer

废弃原因:Finalizer 机制不可靠,可能导致内存泄漏、性能问题等。

替换方案:使用 java.lang.ref.Cleaner 或 try-with-resources。

代码对比

// ❌ 旧代码:使用 finalize() 方法
public class ResourceHolder {
    private NativeResource nativeResource;
    
    public ResourceHolder() {
        nativeResource = new NativeResource();
    }
    
    @Override
    protected void finalize() throws Throwable {
        try {
            nativeResource.release();
        } finally {
            super.finalize();
        }
    }
}

// ✅ 新代码:使用 Cleaner
import java.lang.ref.Cleaner;

public class ResourceHolder implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();
    
    private final Cleaner.Cleanable cleanable;
    private NativeResource nativeResource;
    
    public ResourceHolder() {
        nativeResource = new NativeResource();
        cleanable = cleaner.register(this, new ResourceCleaner(nativeResource));
    }
    
    @Override
    public void close() {
        cleanable.clean();
    }
    
    private static class ResourceCleaner implements Runnable {
        private final NativeResource resource;
        
        ResourceCleaner(NativeResource resource) {
            this.resource = resource;
        }
        
        @Override
        public void run() {
            resource.release();
        }
    }
}

// 使用方式
try (ResourceHolder holder = new ResourceHolder()) {
    // 使用资源
} // 自动调用 close()

使用 PhantomReference 的方案

// ✅ 使用 PhantomReference + ReferenceQueue
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;

public class ResourceCleanupService {
    private final ReferenceQueue<ResourceHolder> queue = new ReferenceQueue<>();
    
    public ResourceCleanupService() {
        Thread cleanerThread = new Thread(() -> {
            while (true) {
                try {
                    Reference<? extends ResourceHolder> ref = queue.remove();
                    // 清理资源
                    ((ResourceHolder.ResourceRef) ref).cleanup();
                    ref.clear();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });
        cleanerThread.setDaemon(true);
        cleanerThread.start();
    }
    
    public void track(ResourceHolder holder) {
        new ResourceRef(holder, queue);
    }
    
    private static class ResourceRef extends PhantomReference<ResourceHolder> {
        private final NativeResource resource;
        
        ResourceRef(ResourceHolder holder, ReferenceQueue<ResourceHolder> queue) {
            super(holder, queue);
            this.resource = holder.nativeResource;
        }
        
        void cleanup() {
            resource.release();
        }
    }
}

3.5 第五类:SecurityManager

废弃原因:SecurityManager 在 Java 17 中被标记为 deprecated for removal(JEP 411),Java 21 中仍然可用但会发出警告。

⚠️ 重要提示:SecurityManager 在 Java 21 中并没有被完全移除,但强烈建议移除相关代码,因为它将在未来的 Java 版本中彻底移除。

替换方案:移除所有 SecurityManager 相关代码。

代码对比

// ❌ 旧代码:使用 SecurityManager
import java.security.SecurityManager;

public class SecurityConfig {
    public static void configureSecurity() {
        SecurityManager sm = new SecurityManager() {
            @Override
            public void checkPermission(java.security.Permission perm) {
                // 自定义权限检查
            }
        };
        System.setSecurityManager(sm);
    }
}

// ✅ 新代码:移除 SecurityManager
public class SecurityConfig {
    // SecurityManager 已不再需要
    // 如果需要权限控制,使用 Java 模块系统或其他机制
}

处理 Spring Boot 应用

// 如果你的 Spring Boot 应用中有 SecurityManager 相关配置
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        // 移除 SecurityManager 相关代码
        // System.setSecurityManager(null);
        SpringApplication.run(Application.class, args);
    }
}

禁用警告(临时方案):

// 方案一:使用 @SuppressWarnings 注解抑制单个警告
@SuppressWarnings("removal")
public void configureSecurity() {
    SecurityManager sm = new SecurityManager();
    System.setSecurityManager(sm);
}

// 方案二:在启动参数中禁用 SecurityManager 移除警告
// -Djava.security.manager=allow
// 注意:这只是临时方案,建议尽早移除 SecurityManager 相关代码

四、Maven modernizer 插件:自动扫描废弃 API

4.1 插件配置

pom.xml 中添加 modernizer 插件:

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>modernizer-maven-plugin</artifactId>
            <version>2.8.0</version>
            <configuration>
                <!-- 指定目标 Java 版本 -->
                <javaVersion>21</javaVersion>
                <!-- 是否在编译时检查 -->
                <failOnViolation>true</failOnViolation>
                <!-- 是否包含测试代码 -->
                <includeTestClasses>true</includeTestClasses>
                <!-- 自定义排除规则(可选) -->
                <exclusions>
                    <exclusion>java.awt.**</exclusion>
                </exclusions>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>modernizer</goal>
                    </goals>
                    <phase>compile</phase>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

4.2 运行扫描

# 扫描主代码
mvn modernizer:modernizer

# 扫描测试代码
mvn modernizer:modernizer -DincludeTestClasses=true

# 只检查不报错
mvn modernizer:modernizer -DfailOnViolation=false

4.3 扫描报告示例

[INFO] --- modernizer-maven-plugin:2.8.0:modernizer (default-cli) @ my-project ---
[INFO] Found 15 violations in main classes
[INFO] Found 3 violations in test classes
[ERROR] org.codehaus.mojo.modernizer.ModernizerViolationException: 
        Found deprecated API usage:
        1. java.security.AccessController.doPrivileged(PrivilegedAction)
           in com.example.service.SecurityService.java:45
        2. java.net.URL.<init>(String)
           in com.example.util.HttpUtils.java:23
        3. java.lang.Thread.stop()
           in com.example.task.WorkerThread.java:78
        ...

五、OpenRewrite:一键迁移脚本

5.1 插件配置

pom.xml 中添加 OpenRewrite 插件:

<build>
    <plugins>
        <plugin>
            <groupId>org.openrewrite.maven</groupId>
            <artifactId>rewrite-maven-plugin</artifactId>
            <version>5.42.0</version>
            <configuration>
                <activeRecipes>
                    <!-- Java 21 升级主配方(包含多个子配方) -->
                    <recipe>org.openrewrite.java.migrate.UpgradeToJava21</recipe>
                </activeRecipes>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>org.openrewrite.recipe</groupId>
                    <artifactId>rewrite-migrate-java</artifactId>
                    <version>2.18.0</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

5.2 UpgradeToJava21 包含的子配方

UpgradeToJava21 是一个伞形配方,包含以下子配方:

子配方功能描述
UpgradeToJava17Java 17 升级(包含 AccessController、URL 构造函数等替换)
RemediateFinalizers替换 finalize() 方法为 Cleaner
RemoveUnusedImports移除未使用的导入
CleanupUnusedVariables清理未使用的变量

💡 提示:如果你只需要修复特定的废弃 API,可以使用 UpgradeToJava17 配方,它已经包含了大部分废弃 API 的修复。

5.3 运行迁移

# 预览迁移效果(不修改文件)
mvn rewrite:dryRun

# 执行迁移(修改文件)
mvn rewrite:run

# 指定特定配方运行
mvn rewrite:run -Drewrite.activeRecipes=org.openrewrite.java.migrate.ReplaceDeprecatedUrlConstructor

5.4 迁移报告示例

[INFO] --- rewrite-maven-plugin:5.42.0:run (default-cli) @ my-project ---
[INFO] Using active recipes:
  - org.openrewrite.java.migrate.UpgradeToJava21
  - org.openrewrite.java.migrate.RemediateFinalizers
  - org.openrewrite.java.migrate.RemoveSecurityManagerCalls
[INFO] 
[INFO] Changes made:
[INFO] 
[INFO] com.example.service.SecurityService.java
[INFO]   - Removed AccessController.doPrivileged call
[INFO]   - Inlined the privileged action body
[INFO] 
[INFO] com.example.util.HttpUtils.java
[INFO]   - Replaced new URL("...") with URI.create("...").toURL()
[INFO] 
[INFO] com.example.task.WorkerThread.java
[INFO]   - Replaced Thread.stop() with interrupt pattern
[INFO]   - Added volatile running flag
[INFO] 
[INFO] Total changes: 15 files modified, 3 classes removed

六、兼容性处理清单

6.1 必须处理的废弃 API

废弃 APIJava 版本处理优先级替换方案
AccessController.doPrivileged17+P0直接调用
Thread.stop/resume/suspend1.2+P0中断模式/ExecutorService
new URL(String)17+P1URI.create(str).toURL()
finalize()9+P1Cleaner / try-with-resources
SecurityManager17+P1移除 / 禁用警告

6.2 需要关注的其他变更

变更类型影响范围处理方式
模块系统增强反射访问受限使用 --add-opens 或重构代码
序列化变更旧序列化格式检查序列化兼容性
JVM 参数变更启动脚本更新 JVM 参数
日期时间 APIDate/Calendar迁移到 java.time

6.3 迁移检查清单

迁移检查清单:
┌─────────────────────────────────────────────────────┐
│                                                     │
│ ✅ 使用 modernizer 插件扫描所有废弃 API              │
│ ✅ 使用 OpenRewrite 批量修复可自动迁移的 API          │
│ ✅ 手动修复 Thread.stop/resume/suspend              │
│ ✅ 替换 finalize() 为 Cleaner                        │
│ ✅ 移除或禁用 SecurityManager                        │
│ ✅ 替换 URL 构造函数为 URI.toURL()                   │
│ ✅ 移除 AccessController.doPrivileged                │
│ ✅ 更新 pom.xml 中的 Java 版本                       │
│ ✅ 更新 IDE 项目配置                                  │
│ ✅ 更新 CI/CD 构建脚本                               │
│ ✅ 更新生产环境 JVM 参数                              │
│ ✅ 运行完整测试套件                                   │
│ ✅ 灰度发布验证                                       │
│                                                     │
└─────────────────────────────────────────────────────┘

七、迁移实战技巧

7.1 渐进式迁移策略

渐进式迁移步骤:
1. 先升级到 Java 17(LTS),处理大部分废弃 API
2. 再升级到 Java 21,处理剩余问题
3. 使用 -Djava.version=21 进行编译测试
4. 使用 --release 21 确保兼容性

7.2 处理第三方依赖问题

// 如果第三方依赖使用了废弃 API,可以:

// 1. 升级依赖版本
<dependency>
    <groupId>com.thirdparty</groupId>
    <artifactId>library</artifactId>
    <version>2.0.0</version> <!-- 使用兼容 Java 21 的版本 -->
</dependency>

// 2. 使用 --add-opens 临时解决
// 在启动参数中添加:
// --add-opens java.base/java.lang=ALL-UNNAMED

// 3. 使用迁移工具修复依赖源码(如果是开源项目)

7.3 测试策略

// 迁移后的测试策略:
// 1. 运行单元测试
// 2. 运行集成测试
// 3. 运行性能测试(检查 Finalizer 移除后的性能变化)
// 4. 运行并发测试(检查线程中断机制是否正确)
// 5. 运行安全测试(检查 SecurityManager 移除后的影响)

八、总结

8.1 废弃 API 替换总结

废弃 API替换方案自动化工具
AccessController.doPrivileged直接调用OpenRewrite
Thread.stop/resume/suspend中断模式/ExecutorServiceOpenRewrite(部分)
new URL(String)URI.create(str).toURL()OpenRewrite
finalize()Cleaner / try-with-resourcesOpenRewrite
SecurityManager移除 / 禁用警告OpenRewrite

8.2 迁移工具对比

工具功能适用场景
modernizer扫描废弃 API发现问题
OpenRewrite批量修复废弃 API自动迁移
IDE 内置工具单个修复精细调整
Lombok简化代码辅助工具

8.3 关键注意事项

关键注意事项:
1. Thread.stop() 的替换需要仔细设计,不能简单替换为 interrupt()
2. SecurityManager 在 Java 21 中仍可用,但建议尽早移除
3. Finalizer 的替换可能影响内存管理,需要充分测试
4. 第三方依赖可能使用废弃 API,需要同步升级
5. 迁移后需要进行全面的测试,特别是并发和性能测试

💡 互动话题:你在 Java 版本迁移中遇到过哪些坑?是如何解决的?欢迎在评论区分享你的经验!


标题:Java 21 生产迁移实战 ②:废弃 API 替换与兼容性处理清单
作者:jiangyi
地址:http://jiangyi.space/articles/2026/07/19/1784435753911.html
公众号:服务端技术精选

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程

取消