写点什么

SpringMVC 框架适配模块及实现原理

  • 2023-06-13
    湖南
  • 本文字数:2398 字

    阅读完需:约 8 分钟

如果不借助 Sentinel 提供的适配主流框架的模块,则在使用 Sentinel 时需要借助 try-catchfinally 将要保护的资源(方法或代码块)包起来,在目标方法或代码块执行之前,调用 ContextUtil#enter 方法及 SphU#entry 方法;在抛出异常时,如果非 BlockException 异常需要调用 Tracer#trace 方法统计异常指标,则在 finally 中需要调用 SphU#entry 方法返回的 Entry 实例的 exit 方法,以及 ContextUtil#exit 方法。


为了节省这些步骤,Sentinel 提供了对主流框架的适配,如适配 Spring MVC、WebFlux、Dubbo、APIGateway 等框架,在 Sentinel 源码之外,Alibaba 的 spring-cloud-starter-alibabasentinel 也为 Sentinel 提供了与 OpenFeign 框架整合的支持。

适配 Spring MVC 框架

Spring MVC 框架是目前使用最多的一个 Web 框架(用 Java 语言编写),而 Sentinel 提供了与 Spring MVC 框架整合使用的适配模块。


本节将介绍如何使用 Spring MVC 适配模块并分析其实现原理。

使用步骤

要使用 Sentinel 提供的 SpringMVC 适配模块,只需要在项目中添加 sentinel-springwebmvc-adapter 模块的依赖即可,借助 WebMvcConfigurer 将 SentinelWebInterceptor 注册到 Spring MVC 框架中,步骤如下。


第一步,在项目中添加 Spring MVC 适配模块的依赖,代码如下:

第二步,编写 WebMvcConfigurer,在 addInterceptors 方法中注入 SentinelWebInterceptor,代码如下:

在创建 SentinelWebInterceptor 时,可以为 SentinelWebInterceptor 添加配置,使用 SentinelWebMvcConfig 封装这些配置。• setBlockExceptionHandler:配置 BlockException 处理器,如果不想配置 BlockException 处理器,则可以在 Spring MVC 的全局异常处理器中处理 BlockException。

  • setOriginParser:注册调用来源(origin)解析器,例如,从请求头中获取 S-user 参数的值作为调用来源名称,在向下游服务发起请求时在请求头写入 S-user 参数。

  • setHttpMethodSpecify:是否需要给资源名称加上 HttpMethod 前缀,例如,对于 GET 接口/hello,如果将 httpMethodSpecify 配置为 false,则资源名称为/hello,否则资源名称为 GET:/hello。

适配原理

Sentinel 适配 Spring MVC 框架是借助 Spring MVC 框架提供的方法拦截器实现的,通过方法拦截器在目标方法被调用之前、调用之后及发生异常时,分别调用 ContextUtil#enter 方法、SphU#entry 方法、Tracer#trace 方法、Entry#exit 方法和 ContextUtil#exit 方法。


Spring MVC 框架的方法拦截器(HandlerInterceptor)的定义如下:

HandlerInterceptor 在 DispatcherServlet#doDispatch 方法中被调用,每个方法的调用时机如

下:

  • preHandle:在调用接口方法之前被调用。

  • postHandle:在接口方法执行完成并返回 ModelAndView 时被调用。

  • afterCompletion:在接口方法执行完成时被调用,无论执行成功或发生异常都会被调用。


因此,Sentinel 可以借助 HandlerInterceptor 与 SpringMVC 框架整合,在 HandlerInterceptor#preHandle 方法中调用 ContextUtil#enter 方法及 SphU#entry 方法,在 afterCompletion 方法中根据方法参数 ex 是否为空来处理异常情况,并且完成 Entry#exit 方法及 ContextUtil#exit 方法的调用。


SentinelWebInterceptor 是 AbstractSentinelInterceptor 的子类,而 preHandle 方法与 afterCompletion 方法在父类中实现,其自身只实现父类定义的一个获取资源名称的抽象方法,即 getResourceName 方法。getResourceName 方法的源码如下:

资源名称的生成过程如下:

  1. 从 HttpServletRequest 参数的属性中获取 HandlerMapping 匹配的 URL。

  2. 如果 UrlCleaner 不为空,则调用 UrlCleaner 的 clean 方法,该方法可将多个接口合并为一个接口。

  3. 根据 SentinelWebMvcConfig 配置对象判断是否需要添加 HttpMethod 前缀,如果需要,则给资源名称拼接前缀。


提示:有些接口的名称形如“/hello/{name}”,如果直接从 HttpServletRequest 参数中获取请求路径,则每个请求获取的 URL 可能会不同,所以需要从 HandlerMapping 属性中获取。


UrlCleaner 的 clean 方法可将多个接口合并为一个接口,例如,若借助 UrlCleaner 将接口/user/create、/user/del 和/user/update 都改为/user/,则可以实现 3 个接口使用同一个限流规则。一般来说,不建议添加 HttpMethod 前缀,因为如果接口使用 @RequestMapping 注解声明,那么想对该接口限流就需要配置多个限流规则。旧项目大多使用 @RequestMapping 注解声明接口方法。例如,对于接口/user/create,用户可能需要为此接口配置 GET:/user/create、POST:/user/create 等多个资源的限流规则。


由于 AbstractSentinelInterceptor 的源码较多,因此分步骤分析。


AbstractSentinelInterceptor#preHandle 方法的源码如下:

  1. 获取资源名称。

  2. 调用 OriginParser#parseOrigin 方法解析调用来源,如从请求头中获取 S-user 参数的值。

  3. 调用 ContextUtil#enter 方法,调用链入口名称为 sentinel_spring_web_context。

  4. 调用 SphU#entry 方法,资源类型为 COMMON_WEB,流量类型为 IN。

  5. 将 SphU#entry 方法返回的 Entry 实例放入 HttpServletRequest 参数的属性表中,方便在 AbstractSentinelInterceptor#afterCompletion 方法中取出。

  6. 如果抛出 BlockException,则说明当前请求被拒绝,需要调用 handleBlockException 方法处理 BlockException。


AbstractSentinelInterceptor#handleBlockException 方法的源码如下:

AbstractSentinelInterceptor#handleBlockException 方法实现的功能:若 SentinelWebMvcConfig 配置了 BlockExceptionHandler,则调用 BlockExceptionHandler#handle 方法处理 BlockException,否则将抛出 BlockException,并由全局处理器处理。


AbstractSentinelInterceptor#afterCompletion 方法的源码如下:

  1. 从 HttpServletRequest 参数的属性表中获取 preHandle 方法中的 Entry 实例。

  2. 调用 AbstractSentinelInterceptor#traceExceptionAndExit 方法。

  3. 调用 ContextUtil#exit 方法。


AbstractSentinelInterceptor#traceExceptionAndExit 方法的源码如下:

AbstractSentinelInterceptor#traceExceptionAndExit 方法实现的功能:当方法执行抛出异常时,调用 Tracer#traceEntry 方法统计异常指标数据。

用户头像

加VX:bjmsb02 凭截图即可获取 2020-06-14 加入

公众号:程序员高级码农

评论

发布
暂无评论
SpringMVC框架适配模块及实现原理_互联网架构师小马_InfoQ写作社区