我们在Controller中定义接口的时候,通常是这样的:@GetMapping("/01")publicStringhello(Mapmap){map.put("name","javaboy");return"forward:/index";}估计很少有人会把接口方法定义为private吧?那我们不禁要问,如果非要定义成私有方法,能行吗?带着这个疑问,我们开始今天的源码解读~我们在使用SpringBoot的时候,经常会看到HandlerMethod类型。比如我们定义一个拦截器,如果拦截目标是一个方法,那么preHandle的第三个参数就是HandlerMethod(下例选自松哥之前面的视频:手把手教你SpringBoot自定义注解):@ComponentpublicclassIdempotentServicekentowireInterceptorimplements{;@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{if(!(handlerinstanceofHandlerMethod)){returntrue;}//省略...returntrue;}//...}我们在阅读SpringMVC源码的时候会看到这个HandlerMethod反复出现,那么什么意思呢?今天想和朋友们讨论一下这个问题,把这个问题搞清楚了,前面的问题大家也就明白了。一、概述可以看到,HandlerMethod体系下的类并不多:HandlerMethod封装了Handler和具体处理请求的Method。InvocableHandlerMethod在HandlerMethod的基础上增加了调用的功能。ServletInvocableHandlerMethod在InvocableHandlerMethod的基础上增加了对@ResponseStatus注解的支持,并增加了对返回值的处理。ConcurrentResultHandlerMethod在ServletInvocableHandlerMethod的基础上增加了对异步结果的处理。基本上就是这四个组成部分。接下来,宋兄就来详细说说这四个组成部分。2.HandlerMethod2.1bridgedMethod在正式介绍HandlerMethod之前,我想先跟大家说说bridgedMethod,因为在HandlerMethod中会涉及到,有的朋友可能没有听说过bridgedMethod,所以宋哥在这里做一个简单的介绍。首先测试一下大家,下面的代码会不会编译报错?publicinterfaceAnimal{voideat(Tt);}publicclassCatimplementsAnimal{@Overridepublicvoideat(Strings){System.out.println("cateat"+s);}}publicclassDemo01{publicstaticvoidmain(String[]args){Animalanimal=newCat();animal.eat(newObject());}}首先,我们定义一个Animal接口,它定义了一个eat方法并声明了一个泛型类型。Cat实现了Animal接口并将通用类型定义为String。当我调用它时,声明的类型是Animal,而实际类型是Cat。这时会调用eat方法,传入Object对象,猜猜会发生什么?如果调用eat方法的时候传入的是String类型,那肯定没问题,但是如果不是String呢?宋大哥先说结论:编译没问题,运行就报错。如果你们在电脑上写上面的代码,就会发现这样的问题。开发工具提示的参数类型其实是Object。以松哥的IDEA为例,如下:可以看到,当我写代码的时候,开发工具会提示参数类型是Object,有的朋友会觉得奇怪,明明是泛型,它怎么会变成一个对象呢?我们可以通过反射检查Cat类中有哪些方法。代码如下:publicclassDemo01{publicstaticvoidmain(String[]args){Method[]methods=Cat.class.getMethods();for(Methodmethod:methods){Stringname=method.getName();Class[]parameterTypes=method.getParameterTypes();System.out.println(name+"("+Arrays.toString(parameterTypes)+")");}}}结果如下:可以看到,在实际运行过程中,其实有两个eat方法,一个参数是String类型,一个参数是Object类型。这是怎么回事?这个参数类型为Object的方法实际上是由Java虚拟机在运行时创建的。这个方法就是我们所说的桥接方法。本节小标题叫bridgedMethod,就是HandlerMethod源码中的变量名。bridge最后多了一个d,意思是变成桥接方法,也就是原来的方法,参数是String。你可以在下面的源码中看到bridgedMethod,你知道这意味着具有相同参数类型的原始方法。2.2HandlerMethod简介接下来我们简单了解一下HandlerMethod。前面我们分析HandlerMapping的时候(见:)涉及到HandlerMethod,而创建HandlerMethod的入口方法是createWithResolvedBean,所以这里从这个方法入手:publicHandlerMethodcreateWithResolvedBean(){Objecthandler=this.bean;if(this.beaninstanceofString){StringbeanName=(String)this.bean;handler=this.beanFactory.getBean(beanName);}returnnewHandlerMethod(this,handler);}这个方法主要是确认handler的类型,如果handler是String类型,重新查找根据beanName从Spring容器中获取handler对象,然后构建HandlerMethod:handlerMethod.method;this.bridgedMethod=handlerMethod.bridgedMethod;this.parameters=handlerMethod.parameters;this.responseStatus=handlerMethod.responseStatus;this.responseStatusReason=handlerMethod.responseStatusReason;this.resolvedFromHandlerMethodde=this.handlerMethodde;}这里的参数比较简单,没什么好说的,只有两个地方值得介绍:参数而responseStatusparameters参数其实就是方法参数,对应的类型是MethodParameter。这里就不贴这个类的源码了。主要给大家说说打包的内容包括:参数序号(parameterIndex)、参数嵌套层次(nestingLevel)、参数类型(parameterType)、参数注解(parameterAnnotations)、参数名称查找器(parameterNameDiscoverer)、参数名称(parameterName)、HandlerMethod还提供了两个内部类来封装MethodParameter,分别是:HandlerMethodParameter:本次封装的方法调用的参数。ReturnValueMethodParameter:这个继承自HandlerMethodParameter,封装了方法的返回值,返回值中的parameterIndex为-1。请注意,两者中的方法都是bridgedMethod。responseStatus主要是处理方法的@ResponseStatus注解。该注解用于描述方法的响应状态码。用法如下:@GetMapping("/04")@ResponseBody@ResponseStatus(code=HttpStatus.OK)publicvoidhello4(@SessionAttribute("name")Stringname){System.out.println("name="+name);}从这段代码中可以看出,@ResponseStatus注解实际上非常不灵活且不实用。当我们定义一个使用接口的时候,很难预料到接口的响应状态码是200。在handlerMethod中,调用其构造方法时,会调用evaluateResponseStatus方法处理@ResponseStatus注解,如下:findMergedAnnotationgetBeanType(),ResponseStatus.class);}if(annotation!=null){this.responseStatus=annotation.code();this.responseStatusReason=annotation.reason();}}可以看到,这段代码相对很简单,找到注解,解析里面的值,赋值给对应的变量。现在小伙伴们应该明白HandlerMethod是什么意思了吧。3、InvocableHandlerMethod从名字就可以知道,InvocableHandlerMethod可以调用HandlerMethod中的具体方法,也就是bridgedMethod。我们先看一下InvocableHandlerMethod中声明的属性:我们已经讨论过这个问题。parameterNameDiscoverer:这个用来获取参数名,在MethodParameter中会用到。dataBinderFactory:这个用来创建WebDataBinder,会在参数解析器中使用。具体的请求调用方法是invokeForRequest,我们来看看:@NullablepublicObjectinvokeForRequest(NativeWebRequestrequest,@NullableModelAndViewContainermavContainer,Object...providedArgs)throwsException{Object[]args=getMethodArgumentValues(request,mavContainer,providedArgs);returndoInvoke}(args)@NullableprotectedObjectdoInvok(Object...args)throwsException{Methodmethod=getBridgedMethod();ReflectionUtils.makeAccessible(方法);try{if(KotlinDetector.isSuspendingFunction(method)){returnCoroutinesUtils.invokeSuspendingFunction(method,getBean()},args)returnmethod.invoke(getBean(),args);}catch(InvocationTargetExceptionex){//省略...}}首先调用getMethodArgumentValues方法依次获取所有参数的值,这些参数值组成一个数组,然后调用doInvoke方法执行,在doInvoke方法中,首先获取bridgedMethod,并将其设置为可见(意思是我们在Controller中定义的接口方法也可以是私有的),一个d然后直接通过反射调用。不看SpringMVC源码的时候就知道接口方法最后肯定是通过反射来调用的。现在,经过层层分析,我们终于在这里找到了反射调用的代码。最后宋哥说一下负责参数解析的getMethodArgumentValues方法:parameters)){returnEMPTY_ARGS;}Object[]args=newObject[parameters.length];for(inti=0;i