You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot下FCM V1初始化FirebaseApp重复报错的解决方法

解决FirebaseApp重复初始化的「already exists」错误

你的问题出在多线程并发场景下的竞态条件:当前代码中,遍历FirebaseApp.getApps()检查实例是否存在,和后续调用initializeApp是两个独立操作,没有原子性保障。当多个线程同时针对同一个projectId调用该方法时,可能会有多个线程都通过了检查,然后同时执行初始化操作,最终触发「FirebaseApp name [project-id] already exists!」的错误。

修复方案:线程安全缓存+细粒度锁初始化

可以借助ConcurrentHashMap缓存已初始化的FirebaseApp实例,同时为每个projectId单独加锁,确保同一项目只会被初始化一次:

import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 类级别缓存,全局唯一存储已初始化的FirebaseApp实例
private final ConcurrentHashMap<String, FirebaseApp> firebaseAppCache = new ConcurrentHashMap<>();
// 为每个projectId维护独立锁,避免全局锁影响性能
private final ConcurrentHashMap<String, Lock> projectLocks = new ConcurrentHashMap<>();

private FirebaseApp getFirebaseApp(final String serverKeyJson, final String projectId) throws IOException {
    // 先从缓存读取,存在则直接返回
    FirebaseApp app = firebaseAppCache.get(projectId);
    if (app != null) {
        return app;
    }

    // 获取当前projectId对应的锁,不存在则创建
    Lock lock = projectLocks.computeIfAbsent(projectId, k -> new ReentrantLock());
    lock.lock();
    try {
        // 加锁后再次检查缓存,防止其他线程已经完成初始化
        app = firebaseAppCache.get(projectId);
        if (app != null) {
            return app;
        }

        // 执行初始化操作
        final ByteArrayInputStream credentialsStream = new ByteArrayInputStream(serverKeyJson.getBytes());
        final FirebaseOptions options = FirebaseOptions.builder()
                .setCredentials(GoogleCredentials.fromStream(credentialsStream))
                .setProjectId(projectId)
                .build();

        app = FirebaseApp.initializeApp(options, projectId);
        firebaseAppCache.put(projectId, app);
        return app;
    } finally {
        lock.unlock();
        // 移除锁释放内存,也可选择保留锁供后续复用
        projectLocks.remove(projectId);
    }
}

核心优化点:

  • ConcurrentHashMap缓存:线程安全地存储实例,避免重复遍历FirebaseApp.getApps(),提升读取效率
  • 细粒度锁:仅针对当前projectId加锁,不会阻塞其他项目的初始化请求,保证服务性能
  • 双重检查机制:加锁前后都校验缓存,彻底规避多线程下的竞态条件

额外注意事项:

  • 若Spring Boot应用为多实例部署,每个实例会维护独立的缓存,这是正常现象(FirebaseApp实例为进程内对象)
  • 确保同一projectId对应的serverKeyJson固定,避免用不同凭证初始化同一项目实例引发异常

内容的提问来源于stack exchange,提问作者Tharindu Rangana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 22:41:11