问题
定义了一个BaseController
abstract class BaseController<T> {
@PostMapping("doSomething")
public Boolean do(@RequestBody T request){
// do xxxx
return true;
}
@PostMapping("try")
public T try(@RequestBody Wrapper<T> request){
// do xxxx
return request.data(); // T对象
}
}
以及一个子类
@RestController
@RequestMapping("/admin/rule")
public class RuleController extends BaseController<RuleRequest>
{
}
期望Application启动之后,调用/admin/rule/doSomething,可以正常处理完成之后,返回true,但是实际上报错404:
No mapping found for HTTP request with URI [/admin/rule/doSomething] in DispatcherServlet with name 'dispatcherServlet'
与此同时,调用/admin/rule/try却能拿到结果,这一差别表现令人困惑。
debug分析
本地启动,并开启debug,发现核心逻辑代码: org.springframework.util.ReflectionUtils#doWithMethods
/**
* Perform the given callback operation on all matching methods of the given
* class and superclasses (or given interface and super-interfaces).
* <p>The same named method occurring on subclass and superclass will appear
* twice, unless excluded by the specified {@link MethodFilter}.
* @param clazz the class to introspect
* @param mc the callback to invoke for each method
* @param mf the filter that determines the methods to apply the callback to
*/
1 public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
2 // Keep backing up the inheritance hierarchy.
3 Method[] methods = getDeclaredMethods(clazz);
4 for (Method method : methods) {
5 if (mf != null && !mf.matches(method)) {
6 continue;
7 }
8 try {
9 mc.doWith(method);
10 }
11 catch (IllegalAccessException ex) {
12 throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
13 }
14 }
15 if (clazz.getSuperclass() != null) {
16 doWithMethods(clazz.getSuperclass(), mc, mf);
17 }
18 else if (clazz.isInterface()) {
19 for (Class<?> superIfc : clazz.getInterfaces()) {
20 doWithMethods(superIfc, mc, mf);
21 }
22 }
23 }
其中,对于Line3 getDeclaredMethods,只获取到了一个方法RuleController#do,继续向下走,发现do方法的判定条件 !mf.matches(method) = true。这里会检查此方法是否为用户自定义的方法,如果不是,则跳过处理,逻辑为:不为bridge方法,并且不是Object对象。
public static final ReflectionUtils.MethodFilter USER_DECLARED_METHODS = new ReflectionUtils.MethodFilter() {
public boolean matches(Method method) {
return !method.isBridge() && method.getDeclaringClass() != Object.class;
}
};
RuleController自身的方法处理完成之后,Line16 doWithMethods接着处理父类的方法,其实还是这个方法处理,相当于递归。但是对于父类来说,getDeclaredMethods变成了两个,同时由于当前已经是BaseController层级,bridge方法的判定失效了,这两个方法会双双进入Line9的处理逻辑里。
Line9的处理逻辑如下:
@Override
public void doWith(Method method) {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
}
}
do方法和try方法,最终的区别就体现在了这里:
对于do方法来说,specificMethod是RuleController#do,bridgedMethod是BaseController#do,两者不一致,因此不做mapping处理;
对于try方法来说,specificMethod与bridgedMethod是一致的,都是BaseController#try,成功完成map处理。
问题解决
所以debug之后,关键问题就出现了,为什么getDeclaredMethods,只返回一个方法?
借助万能的stackOverflow之后,找到了原因,问题指引
最终,只需要对BaseController加上public修饰,两个方法就都不会被RuleController.getDeclaredMethods获取到了,成功mapping两个uri
其他
其实一直以来,以为getDeclaredMethods不会返回父类方法,这么看来也不是绝对的。 另外,debug过程中学习了一波bridgeMethod的由来,每天一个小知识get!