我们在 Controller 中定义接口的时候,一般都是像下面这样:,估计很少有人会把接口方法定义成 private 的吧?那我们不禁要问,如果非要定义成 private 的方法,那能运行起来吗?,带着这个疑问,我们开始今天的源码解读~,在我们使用 Spring Boot 的时候,经常会看到 HandlerMethod 这个类型,例如我们在定义拦截器的时候,如果拦截目标是一个方法,则 preHandle 的第三个参数就是 HandlerMethod:,我们在阅读 SpringMVC 源码的时候,也会反复看到这个 HandlerMethod,那么它到底是什么意思?今天我想和小伙伴们捋一捋这个问题,把这个问题搞清楚了,前面的问题大家也就懂了。,
,可以看到,HandlerMethod 体系下的类并不多:,HandlerMethod,封装 Handler 和具体处理请求的 Method。,InvocableHandlerMethod,在 HandlerMethod 的基础上增加了调用的功能。,ServletInvocableHandlerMethod,在 InvocableHandlerMethod 的基础上增了对 @ResponseStatus 注解的支持、增加了对返回值的处理。,ConcurrentResultHandlerMethod,在 ServletInvocableHandlerMethod 的基础上,增加了对异步结果的处理。,基本上就是这四个,接下来松哥就来详细说一说这四个组件。,2.1 bridgedMethod,在正式开始介绍 HandlerMethod 之前,想先和大家聊聊 bridgedMethod,因为在 HandlerMethod 中将会涉及到这个东西,而有的小伙伴可能还没听说过 bridgedMethod,因此松哥在这里做一个简单介绍。,首先考考大家,下面这段代码编译会报错吗?,首先我们定义了一个 Animal 接口,里边定义了一个 eat 方法,同时声明了一个泛型。Cat 实现了 Animal 接口,将泛型也定义为了 String。当我调用的时候,声明类型是 Animal,实际类型是 Cat,这个时候调 eat 方法传入了 Object 对象大家猜猜会怎么样?如果调用 eat 方法时传入的是 String 类型那就肯定没问题,但如果不是 String 呢?,松哥先说结论:编译没问题,运行报错。,如果小伙伴们在自己电脑上写出上面这段代码,你会发现这样一个问题,开发工具中提示的参数类型竟然是 Object,以松哥的 IDEA 为例,如下:,
,大家看到,在我写代码的时候,开发工具会给我提示,这个参数类型是 Object,有的小伙伴会觉得奇怪,明明是泛型,怎么变成 Object 了?,我们可以通过反射查看 Cat 类中到底有哪些方法,代码如下:,运行结果如下:,
,可以看到,在实际运行过程中,竟然有两个 eat 方法,一个的参数为 String 类型,另一个参数为 Object 类型,这是怎么回事呢?,这个参数类型为 Object 的方法其实是 Java 虚拟机在运行时创建出来的,这个方法就是我们所说的 bridge method。本节的小标题叫做 bridgedMethod,这是 HandlerMethod 源码中的变量名,bridge 结尾多了一个 d,含义变成了被 bridge 的方法,也就是参数为 String 的原方法,大家在接下来的源码中看到了 bridgedMethod 就知道这表示参数类型不变的原方法。,2.2 HandlerMethod 介绍,接下来我们来简单看下 HandlerMethod。,在我们前面分析 HandlerMapping 的时候(参见:),里边有涉及到 HandlerMethod,创建 HandlerMethod 的入口方法是 createWithResolvedBean,因此这里我们就从该方法开始看起:,这个方法主要是确认了一下 handler 的类型,如果 handler 是 String 类型,则根据 beanName 从 Spring 容器中重新查找到 handler 对象,然后构建 HandlerMethod:,这里的参数都比较简单,没啥好说的,唯一值得介绍的地方有两个:parameters 和 responseStatus。,parameters,parameters 实际上就是方法参数,对应的类型是 MethodParameter,这个类的源码我这里就不贴出来了,主要和大家说一下封装的内容包括:参数的序号(parameterIndex),参数嵌套级别(nestingLevel),参数类型(parameterType),参数的注解(parameterAnnotations),参数名称查找器(parameterNameDiscoverer),参数名称(parameterName)等。,HandlerMethod 中还提供了两个内部类来封装 MethodParameter,分别是:,注意,这两者中的 method 都是 bridgedMethod。,responseStatus,这个主要是处理方法的 @ResponseStatus 注解,这个注解用来描述方法的响应状态码,使用方式像下面这样:,从这段代码中大家可以看到,其实 @ResponseStatus 注解灵活性很差,不实用,当我们定义一个接口的时候,很难预知到该接口的响应状态码是 200。,在 handlerMethod 中,在调用其构造方法的时候,都会调用 evaluateResponseStatus 方法处理 @ResponseStatus 注解,如下:,可以看到,这段代码也比较简单,找到注解,把里边的值解析出来,赋值给相应的变量。,这下小伙伴们应该明白了 HandlerMethod 大概是个怎么回事。,看名字就知道,InvocableHandlerMethod 可以调用 HandlerMethod 中的具体方法,也就是 bridgedMethod。我们先来看下 InvocableHandlerMethod 中声明的属性:,主要就是这三个属性:,具体的请求调用方法是 invokeForRequest,我们一起来看下:,首先调用 getMethodArgumentValues 方法按顺序获取到所有参数的值,这些参数值组成一个数组,然后调用 doInvoke 方法执行,在 doInvoke 方法中,首先获取到 bridgedMethod,并设置其可见(意味着我们在 Controller 中定义的接口方法也可以是 private 的),然后直接通过反射调用即可。当我们没看 SpringMVC 源码的时候,我们就知道接口方法最终肯定是通过反射调用的,现在,经过层层分析之后,终于在这里找到了反射调用代码。,最后松哥再来说一下负责参数解析的 getMethodArgumentValues 方法:,是不是,很 easy!,ServletInvocableHandlerMethod 则是在 InvocableHandlerMethod 的基础上,又增加了两个功能:,Servlet 容器下 Controller 在查找适配器时发起调用的最终就是 ServletInvocableHandlerMethod。,这里的处理核心方法是 invokeAndHandle,如下:,事实上,ServletInvocableHandlerMethod 还有一个子类 ConcurrentResultHandlerMethod,这个支持异步调用结果处理,因为使用场景较少,这里就不做介绍啦。
© 版权声明
文章版权归作者所有,未经允许请勿转载。