浅尝源码中设计模式的哪些小九九

# 上一篇 : 责任链设计及各种变体,彻底搞懂责任链

源码中的责任链

源码追踪

  • 上面说了这么多无非都是对责任链的理解与变换,纸上得来终觉浅,接下来我们结合源码看看他们是如何运用责任链思想的。说到源码+责任链,我的脑海中立马浮现了如下一些关键词
关键词
Filter
StringBuilder
SpringMVC
Workflow
  • 接下来我们就从 SpringMVC 的源码中看看责任链是如何应用的。开始之前我们需要强调一点的是 SpringBoot 中请求的入口是 org.springframework.web.servlet.DispatcherServlet 这个类,接下来我们都是基于这个入口进行展开的,前置知识还得请读者自行补充下哦。
  • doDispatch 中我们能够看到开头会定义 4 个局部变量,这四个变量我们咋一看就能够看到 chain 而这个对应的就是 链条 的意思。这点可能有的读者会疑问,这样阅读源码也太随意了吧!!!,的确是,但是我自己在读这段代码的时候是全局看了大体逻辑,分别去看每一处的作用才发现 HandlerExecutionChain 是运用责任链模式的,只是现在向外输出的时候才以这种方式拉开入口的。
  • 我们看看 HandlerExecutionChain 这个类设计的方法调用
  • 我们发现在 dispatcher 方法中涉及了三处方法的调用。我们在看看 HandlerExecutionChain 这个类的代码吧。
  • 我们能够发现他的内部对象基本上和我们上面谈及的责任链模式中对象是吻合的,他的内部使用了 Java 的特性 List 来实现链表功能,而我们上述类似使用的 C 中的指针来实现链表的,实现方式不一样但是核心思想是一致的。

  • 接下来看看这些对象是如何构建的

  • 怎么样,他的构造函数实际上就是在初始化上面这些属性。和我们的责任链设计基本一致
  • 观看下他内部的方法基本上是和我们责任链中 AbstractHandler 对应的。因为我们主要看如何使用责任链模式,所以 HandlerExecutionChain 内部的方法我们暂时不需要过于关注。
  • 上面我们说了在 DispatcherServletHandlerExecutionChain 会使用到三个方法,他们分别是责任链上节点提供的三个同步业务的方法,其实分别就是对请求的不同阶段的一个拦截。三个方法内部都是一样的逻辑,分别执行责任链上每个节点对应的方法,HandlerInterceptor 接口在 web 开发中非常常用,里面有 preHandle()postHandle()afterCompletion() 三个方法,实现这三个方法可以分别在调用"Controller"方法之前,调用"Controller"方法之后渲染"ModelAndView"之前,以及渲染"ModelAndView"之后执行。所以这里我们只看其中 1 个方法的代码:
  • 不知道你看出来没有,在 HandlerExecutionChain 内部的 applyPreHanle 中和我们之前的 AbstractHandler 中 connect 方法基本上逻辑是一样的,都是遍历职责链上的节点进行执行。只不过在 HandlerExecutionChain 中上一个节点执行的结果会影响到是否继续路由到下一个节点执行。
  • 他的请求执行基本上和我们上面流程执行的逻辑一样
  • 所以说从代码结构上出发能够帮助我们快速理解逻辑。接下来我们在看看 dispatcher 这个方法的功能吧。
  • 上面是我将源码中的跟本次主题不相关的代码删除之后的伪代码,怎么样,这样看下来明白了 DispatcherServlet 在责任链中的作用了吧,他就是我们之前的 Test 类,可以理解成他就是实际的责任链使用者。
  • 回到责任链场景中,在 DispatcherServlet 中我们不需要关注 request 对象需要经过什么样的处理,我只关心我给了你 request , 你给我返回结果,就 OK 了。至于内部什么请求转发到 controller ,转发之前做权限验证、结果响应后统一格式等等需求都跟我没关系,需要实现实现我的 HandlerInterceptor 然后注册到我的责任链上就行了。
  • 其实对于 DispatcherServlet 来说业务非常的简单,请求拦截、发起请求、请求结束、数据封装就干了这四件事,其中三件需要借助我们的 HandlerExecutionChain 来完成。剩下的一件发起请求就涉及到我们的 RequestHandlerMapping 来处理请求映射问题。
  • 关于 HandlerAdapter Spring 使用的是适配器模式来实现的,至于适配器模式我们有专门的专题去讨论,这里我们简单梳理下 HandlerAdapter
  • 这个结构也非常的简单,其中只有 RequestMappingHandlerAdapter 有点特殊,因为他的处理比较麻烦。他主要是处理我们经常使用创建接口的方式,通过 RequestMapping 注解开启接口的映射。其他四种主要解决特定情况下的接口,大多都是实现指定类型的接口的方法,所以他们的方法比较固定,这点我们看看源码就能看出来。
适配器 作用
SimpleControllerHandlerAdapter 处理实现了 Controller 接口的 handler
HandlerFunctionAdapter 处理 HandlerFunction 类型的接口
HttpRequestHandlerAdapter 处理实现了 HttpRequestHandler 接口
SimpleServletHandlerAdapter 处理实现了 Servlet 接口的 handler
  • 好了,适配器我们就说这么多,总之我们就记住上面我们提到的 DispatcherServlet 中四个步骤,有三个步骤是利用责任链模式通过 HandlerInterceptor 来拦截执行的。

基于设计模式的思考

  • 也许你搭建过项目的基础架构接触过 HandlerInterceptor ,如果你没有接触过,进过今天从设计模式的角度简单的剖析了 DispatcherServlet 之后,你能明白 HandlerInterceptor 的使用场景吗?

应用场景

  1. 日志记录,可以记录请求信息的日志,以便进行信息监控、信息统计等。
  2. 权限检查:如登陆检测,进入处理器检测是否登陆,如果没有直接返回到登陆页面。
  3. 性能监控:典型的是慢日志。

SpringBoot 中自定义 HandlerInterceptor

  • 我们能看到在 SpringBoot 中一共有 15 个 HandlerInterceptor 的实现类。其中 HandlerInterceptorAdapter 是一个抽象类,这里还是需要提一嘴,这个是典型的接口适配器模式。他的好处我们不需要实现 HandlerInterceptor 接口的所有方法,我们只需要继承 HandlerInterceptorAdapter 并实现我们关心的方法,而不用像实现 HandlerInterceptor 那样实现接口中所有方法,这个我们在适配器模式中详细说说。
  • 所以想要在 SpringBoot 中实现自定义的 HandlerInterceptor 我们可以选择实现 HandlerInterceptor 也可以选择 extends HandlerInterceptorAdapter 类。
java 复制代码
public class UserLoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行了拦截器的preHandle方法");
		return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行了拦截器的postHandle方法");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行了拦截器的afterCompletion方法");
    }
}
  • 或者
java 复制代码
public class TestFilter extends HandlerInterceptorAdapter {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		logger.info("request请求地址path[{}] uri[{}]", request.getServletPath(),request.getRequestURI());
		return true;
	}
}
  • 准备好后我们只需要注册下即可。
java 复制代码
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 可添加多个,/**是对所有的请求都做拦截
        registry.addInterceptor(new TestFilter())
                .addPathPatterns("/**")
                .excludePathPatterns("/login", "/register");
        registry.addInterceptor(new UserLoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login", "/register");
    }
 
    ....
}
  • 两种方式都可以,但是继承的方式在实现上比较简洁,你需要拦截什么事件就实现对应的拦截事件即可,更加的聚焦自己的业务。

完美退场

  • 天下无不散之宴席,到这里我相信你应该更加了解责任链模式了,同时应该也更加了解 SpringMVC 的处理流程了,当然这里也只是抛砖引玉,想要彻底完整的了解 SpringMVC , 光靠这点知识远远不够的。
  • 最后如果你觉得文章写的还不错,能够点赞+收藏呢?有了数据才能帮助更多的人,这样才能让你第一时间收到此类文章。
相关推荐
Asthenia041224 分钟前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz96542 分钟前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom1 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide1 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9651 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04122 小时前
Spring 启动流程:比喻表达
后端
Asthenia04122 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua2 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫