背景
为什么要在interceptor层获得方法名称呢?在分布式链路系统中我们需要在MVC框架层埋点,统计方法调用的耗时、trace信息等,目前公司内部没有统一的MVC框架,但是大多数都是使用的SpringMVC.所以我们在Interceptor这一层埋点就ok。在这里可以统计到方法调用完的耗时信息,同时也可以得到用户自定义的埋点信息。在这个过程中踩了一些坑,也尝试了各种方法
Interceptor介绍
1 | /* |
该handler是什么呢?通过DispatcherServlet
类源码我们可以看到该handler是HandlerExecutionChain
中的Object对象,顾名思义,该类代表了这次request请求的执行链,里面包括了这次执行中的所有interceptor。那么这个handler对象是Method对象吗?并不完全是这样的…
高版本SpringMVC(3.1+)
那么HandlerExecutionChain是怎么初始化的呢?它是靠HandlerMapping来初始化的,HandlerMapping的实例可以自己配置,或者使用默认配置,SpringMVC会默认的加载DispatcherServlet.properties配置文件中的这几种配置
1 | org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver |
HandlerMapping的工作就是将request和handler映射起来,但是我们会有多种方式,比如通过controller的名称、或者在xml中配置、又或者使用annotation的方式。所以mapping有很多种,当然也可以配置多个HandlerMapping,SpringMVC通过适配器模式为你找到匹配的HandlerMapping。那么这个Handler究竟是什么呢?
在AbstractUrlHandlerMapping
抽象类的registerHandler方法可以找到答案,handler默认是Controller实例,通过beanName被抽象类获取到实例(controller应该都会加载到容器这是毋庸置疑的)。那么结局就有点尴尬了,拿到Controller实例没什么大的作用。根本拿不到对应的方法。
但是SpringMVC3.1以上版本annotation-driven
配置把DefaultAnnotationHandlerMapping
和AnnotationMethodHandlerAdapter
默认修改成了RequestMappingHandlerMapping
和对应的adapter。后者的一系列类把request和Method对象Mapping在了一起。通过以下方法使用
- 使用annotation-driven配置xml,可以自动注入RequestMappingHandlerMapping和adapter
- 手动配置RequestMappingHandlerMapping的bean
使用了RequestMappingHandlerMapping之后,handler的实例就变成了HandlerMethod
这个对象,我们可以直接获得方法名称,皆大欢喜!
低版本SpringMVC(3.1以下)
如果是低版本的SpringMVC 那就没办法了,只能拿到Controller实例的对象,这里心生一计,既然能得到Controller对象,是否可以通过request中的url,在通过反射拿到所有方法的注解值然后mapping到方法呢?好想是可以的,但是这里有一个问题,就是url匹配的问题,SpringMVC包含了多种url匹配,比如RESTFUL,还有各种匹配格式,非常繁琐。要么自己重写SpringMVC的匹配,要么就使用内部的匹配方法。这一点也提醒了我,SpringMVC最后肯定会通过一种方式找到对应的方法然后invoke的。这也就是adapter的责任。看看DispatcherServlet(前两个)源码细节
1 | // 1.Determine handler adapter for the current request. |
流程大概是这样的:
- 通过handler对象找到对应的adapter对象,同时初始化自己的methodResolver,同时放入到adapter的一个map
当中初始化过程详细见 ServletHandlerMethodResolver
和它的父类HandlerMethodResolver
- adapter调用handle方法的时候,传入request,调用resolveHandlerMethod(request)方法,通过SpringMVC自己的匹配规则,最终得到Method对象。
好了,终于找到了url匹配的方法,这个方法要用两个东西,一个是handler,一个是request。我们要如何使用它呢?由于adapter在拦截器之前执行,所以方法映射都已经初始化完毕了。所以我们只能使用初始化完毕之后的map对象,这里就只有使用反射:大概的代码是这样的。
1 | ApplicationContext context = applicationContext; |
这样就能完美的得到被调用的方法名称了,回顾一下整个流程,看起来很简单,其实是一个源码探究的过程,SpringMVC整个过程还是非常复杂的,但是扩展性有些地方很好,有些地方却差强人意。这种方式不好的地方就死对Spring使用了反射,这种侵入性还是有一点,不过我验证之后发现,从2.5开始每个版本的AnnotationMethodHandlerAdapter类都有此方法,所以还算合格。还有一个缺点就是目前只正对annotaion方式做了除了,比如基本的SimpleUrlHandlerMapping等暂时还没有做处理。那么在整个途中还延伸了一种AOP的方法
利用AspectJ AOP代理
想到拦截器,自然也想到了代理机制,我们使用AOP环绕或者before、after的方式给方法埋点是否更好呢?其实这种方式对Controller层都会织入我们的ASpectJ代码。使用最简单的方式就行给加上Trace注解的方法都织如aop代理:
1 | @Aspect |
编译之后,代码大概会是这样:
1 | @RequestMapping({"/hello"}) |
总结
- 文章并没有详细的深入到SpringMVC的源码中去,建议读者自行去调试。只是给了大家一个解决问题的思路
- 有不妥之处,望斧正!不胜感激