背景
需求是这样的:对网关层指定接口(url)的调用情况做日志记录,日志需要根据 controller 层方法的入参和返回值动态生成。
如果只是记录 url 的调用情况可以直接使用 HandlerInterceptor
实现,但需求中需要能够访问入参和返回值,虽然通过拦截器提供的接口也可以解析到请求参数以及返回值,但都需要对这些参数进行一定的处理,因此直接在 Controller 层方法上处理是最直接的。
思路是找到 Spring MVC 中对 Controller 层方法最终调用的 API,然后看看能否接管调用过程,添加自己的业务逻辑。
从 DispatcherServlet#doDispatch
开始寻找扩展点
Spring MVC 的入口 servlet 是 DispatcherServlet,调用链是 doService -> doDispatch,servlet 和 controller 关联到一起的是 HandlerAdapter
接口,通过 DispatcherServlet#getHandlerAdater
方法遍历配置的 HandlerAdapter
集合,挨个检查其 HandlerAdapter#supports
方法,找到支持的 HandlerAdapter
,然后调用其 HandlerAdapter#handle
方法。
DispatcherServlet#getHandlerAdater
方法最终找到的 HandlerAdapter
是 RequestMappingHandlerAdapter
RequestMappingHandlerAdapter#invokeHandlerMethod
DispatcherServlet#doDispatch
中找到 RequestMappingHandlerAdapter
之后的调用链如下:
handle -> handleInternal -> invokeHandlerMethod,调用都发生在RequestMappingHandlerAdapter
类中,在最后的 RequestMappingHandlerAdapter#invokeHandlerMethod
方法中,处理的主流程转移到了ServletInvocableHandlerMethod
类:
InvocableHandlerMethod#doInvoke
RequestMappingHandlerAdapter#createInvocableHandlerMethod
方法返回了 ServletInvocableHandlerMethod
的实例,接下来的处理调用链如下:
invokeAndHandle -> invokeForRequest -> doInvoke,调用都发生在类 ServletInvocableHandlerMethod
内部,其中invokeForRequest
和doInvoke
方法在 InvocableHandlerMethod
类中定义,ServletInvocableHandlerMethod
继承自InvocableHandlerMethod
,终于定位到最终调用 controller 层方法的入口方法是 ServletInvocableHandlerMethod#doInvoke
。
/**
* Invoke the handler method with the given argument values.
*/
protected Object doInvoke(Object... args) throws Exception {
// 省略
}
doInvoke
方法签名如上,入参 args 即为 controller 层入参,args 数组下标顺序与 controller 层方法入参定义顺序一致,返回值类型为 Object,即为 controller 层方法执行返回值。
封装扩展点
通过上述源码寻找之后,找到最终的扩展点为 InvocableHandlerMethod#doInvoke
方法,已经知道 InvocableHandlerMethod 与 DispatcherServlet 通过 RequestMappingHandlerAdapter 相关联,RequestMappingHandlerAdapter 通过 createInvocableHandlerMethod
方法获得 InvocableHandlerMethod 实例,那么我们只需覆写 createInvocableHandlerMethod
方法,提供自己的 InvocableHandlerMethod 实现,并在 InvocableHandlerMethod#doInvoke
中添加自己的实现,以完成业务需求。
doInvoke 可按如下方式覆写:
public class HandlerMethodWrapper extends ServletInvocableHandlerMethod {
@Override
protected Object doInvoke(Object... args) throws Exception {
before(args);
Object result = super.doInvoke(args);
after(result, args);
return result;
}
}
抽离出接口
可以抽离如下接口来进一步封装操作:
public interface HandlerMethodPostProcessor {
/**
* controller 层方法参数装配完成,方法执行前调用
*
* @param handlerMethod HandlerMethod
* @param args controller 方法参数
*/
default void postProcessorBeforeInvoke(ServletInvocableHandlerMethod handlerMethod, Object... args) {
}
/**
* controller 方法执行完成,结果输出前调用(HandlerMethodReturnValueHandler#handleReturnValue 调用之前)
*
* @param handlerMethod HandlerMethod
* @param args controller 方法参数
* @param result 方法执行结果
*/
default void postProcessorAfterInvoke(Object result, ServletInvocableHandlerMethod handlerMethod, Object... args) {
}
}
doInvoke 最终封装如下:
配置(@Configuration
+ WebMvcRegistrations#getRequestMappingHandlerAdapter
)
最后需要将 HandlerMethodWrapper 通过自定义的 RequestMappingHandlerAdapter 注册到 spring MVC 中:
实现
WebMvcRegistrations#getRequestMappingHandlerAdapter
方法,提供自定义的 RequestMappingHandlerAdapter
,并覆盖其 createInvocableHandlerMethod
方法,返回 HandlerMethodWrapper
。
上图 3 标记处借助 HandlerMethodPostProcessorComposite
类,参数装配时所有实现了 HandlerMethodPostProcessor
接口的 bean 都会注入 HandlerMethodPostProcessorComposite
中。
完整代码已上传 GitHub,可以在 这里 找到