问题分析
在将 SpringBoot 项目的 Kotlin 版本更新至 1.3+ 之后,项目启动时抛出了如下异常:
java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'xxxx' method
抛出异常方法是一个有默认参数的方法,形如:
fun func(str: String = ""){
// do somethings
}
从 Java 的视觉看,在字节码反映出的是会生成对应的重载方法。但在 Kotlin1.3 编译出的字节码中,这个生成的重载方法相对 Kotlin1.3 相较之前少了一个方法标识 ACC_BRIDGE
,即生成的方法不再是桥接方法。
// 使用javap -verbose 查看class文件
// Kotlin1.3
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNTHETIC
// Kotlin1.2
flags: ACC_PUBLIC, ACC_STATIC, ACC_BRIDGE, ACC_SYNTHETIC
在 Spring MVC 启动前,进行 MVC 接口方法和 RequestMapping
的映射注册,对 @Controller
类下的 Mapping
方法进行扫描并找出真实的 Method
对象,其核心方法如下:
// BridgeMethodResolver类方法
public static Method findBridgedMethod(Method bridgeMethod) {
if (bridgeMethod != null && bridgeMethod.isBridge()) {
List<Method> candidateMethods = new ArrayList();
Method[] methods = ReflectionUtils.getAllDeclaredMethods(bridgeMethod.getDeclaringClass());
Method[] var3 = methods;
int var4 = methods.length;
for(int var5 = 0; var5 < var4; ++var5) {
Method candidateMethod = var3[var5];
if (isBridgedCandidateFor(candidateMethod, bridgeMethod)) {
candidateMethods.add(candidateMethod);
}
}
if (candidateMethods.size() == 1) {
return (Method)candidateMethods.get(0);
} else {
Method bridgedMethod = searchCandidates(candidateMethods, bridgeMethod);
if (bridgedMethod != null) {
return bridgedMethod;
} else {
return bridgeMethod;
}
}
} else {
return bridgeMethod;
}
}
因为没有了 ACC_BRIDGE
标志,即 isBridge()
会返回 false
,所以默认参数对应的 Method
会进入到 if 分支,对于进入该分支的 Method
对象,经过检查后会加入备选列表 candidateMethods
中并等待返回。检查方法逻辑如下:
private static boolean isBridgedCandidateFor(Method candidateMethod, Method bridgeMethod) {
return !candidateMethod.isBridge() && !candidateMethod.equals(bridgeMethod) && candidateMethod.getName().equals(bridgeMethod.getName()) && candidateMethod.getParameterTypes().length == bridgeMethod.getParameterTypes().length;
}
显然,方法会通过检查并最终完成注册,这就导致同一个 RequestMapping
下注册了两个方法,导致应用的启动异常。
解决方案
- Kotlin 编译等级降到 1.2
- SpringBoot 版本升级到 2.x.x 以上