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
相关产品推荐
相关产品推荐

