使用Java调用Firebase FCM HTTP v1 API时,通过OAuth2获取AccessToken出现NoSuchMethodError异常的问题
根据你描述的场景和报错信息,这个问题的核心是依赖版本冲突,具体是Guava(Google核心工具库)的版本不兼容导致的,下面是详细的分析和解决方案:
问题根源
java.lang.NoSuchMethodError: com.google.common.util.concurrent.Futures.getDone(...)这个异常,本质是运行时找不到Futures.getDone方法——这个方法是在Guava 20.0及以上版本才新增的。
你引入的firebase-admin:9.2.0本身依赖的是高版本的Guava,但你的项目中可能存在其他依赖,它们通过传递依赖引入了低于20.0的旧版Guava,导致运行时旧版Guava覆盖了新版,进而找不到这个方法。
解决方案
1. 定位冲突的Guava依赖
首先用Maven命令生成依赖树,找出所有引入Guava的依赖及其版本:
mvn dependency:tree | grep guava
执行后会输出类似这样的结果:
[INFO] +- com.google.guava:guava:jar:20.0:compile [INFO] +- xxx:xxx:jar:1.0:compile [INFO] | \- com.google.guava:guava:jar:19.0:compile
找到其中版本低于20.0的条目,这就是冲突的来源。
2. 排除旧版Guava的传递依赖
对于引入旧版Guava的那个依赖,在pom.xml中添加exclusions,排除它的Guava传递依赖:
<dependency> <!-- 这里替换成你找到的引入旧版Guava的依赖坐标 --> <groupId>xxx</groupId> <artifactId>xxx</artifactId> <version>xxx</version> <exclusions> <exclusion> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </exclusion> </exclusions> </dependency>
3. 显式声明兼容的Guava版本
为了彻底避免冲突,可以直接在pom.xml中显式指定firebase-admin:9.2.0兼容的Guava版本(该版本兼容的Guava版本为31.1-jre):
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency>
4. 验证并刷新依赖
执行以下命令清理并重新下载依赖,确保生效:
mvn clean install -U
再次执行mvn dependency:tree | grep guava,确认所有Guava的版本都是你指定的高版本。
代码优化建议
另外,你的getAccessToken方法可以做一点优化:把GoogleCredentials实例缓存起来,避免每次都读取本地文件和初始化,提升性能:
import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import java.io.FileInputStream; import java.io.IOException; import java.util.Arrays; public class FcmTokenUtils { private static final String MESSAGING_SCOPE = "https://www.googleapis.com/auth/firebase.messaging"; private static final String[] SCOPES = {MESSAGING_SCOPE}; // 缓存Credentials实例,避免重复初始化 private static GoogleCredentials cachedCredentials; // 静态代码块初始化Credentials,只执行一次 static { try { cachedCredentials = GoogleCredentials.fromStream(new FileInputStream("service.json")) .createScoped(Arrays.asList(SCOPES)); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("初始化Firebase凭证失败", e); } } private static String getAccessToken() throws IOException { cachedCredentials.refreshIfExpired(); AccessToken token = cachedCredentials.getAccessToken(); return token.getTokenValue(); } }
额外说明
如果你的IDE是IntelliJ IDEA,可以安装Maven Helper插件,它能更直观地展示依赖冲突,一键排除冲突依赖,操作更高效。
备注:内容来源于stack exchange,提问作者user2166934

