当前位置: 首页 > 科技观察

春节期间用责任链模型重构了业务代码

时间:2023-03-21 00:20:40 科技观察

本文转载自微信公众号“源码兴趣圈”,作者龙泰。转载本文请联系源码兴趣圈公众号。前言文章开头提出了一个常见的问题。学习设计模式有什么作用?设计模式主要是处理代码的复杂性,使其满足开闭原则,提高代码的可扩展性。另外,学习的设计模式必须在业务代码中实现,只是理论没有实现,所以不可能真正掌握并灵活运用设计模式。这篇文章主要讲责任链设计模式。在阅读Mybatis的源码时,Interceptor拦截器主要使用责任链。当时看完,印象很深(内心OS:还可以这样玩)。文章从基本概念入手,在一波Mybatis源码中分析了它是如何使用的,最后根据“习惯”设计了一个真正的业务。场景中应用责任链设计模式的概要如下:什么是责任链模式?完成真正的责任链业务场景设计。MybatisInterceptor实现了底层的责任链模式。总结什么是责任链模式?比如拦截器可以在SpringMvc中定义,并且可以定义多个。当用户发起请求时,如果顺利的话,请求会经过所有的拦截器,最终到达业务代码逻辑。SpringMvc拦截器设计采用责任链模型。为什么它顺利通过所有拦截器?因为请求不满足拦截器定制的规则会被回调,但这并不是责任链模式的唯一处理方式。继续往下看。在责任链模式下,多个处理器(指上面的拦截器)依次处理同一个请求。一个请求先由A处理器处理,然后传递给B处理器,B处理器处理后再传递给C处理器,依此类推,形成一条链,链上的每个处理器负责自己的处理器ChainofResponsibility模式下由多个处理器组成的链在处理请求时有两种处理方式:请求会被所有处理器处理,没有中途终止。这里,参考MyBatis拦截处理器的理解是,在处理器链执行请求中,当一个处理器被执行时,如果不满足自制的规则,就会停止进程,其余未执行的处理器不会被执行执行。理解请参考SpringMvc拦截器,这里以代码的形式对两种处理方式进行了解答,方便读者更好的理解。先看第一个,请求会被所有处理器执行图1责任链模式一种实现IHandler负责抽象处理器行为,handle()是不同处理器具体需要执行的方法,HandleA和HandleB是具体的需要执行的处理器类,HandlerChain是将处理器串成一条链执行的处理器链。publicclassChainApplication{publicstaticvoidmain(String[]args){HandlerChainhandlerChain=newHandlerChain();newHandlerB()));handlerChain.handle();/***程序执行结果:*HandlerAprint:ExecuteHandlerA*HandlerBprint:ExecuteHandlerB*/}}这种责任链执行方式会把所有的processor执行一次,不会中断。这种类型被Mybatis拦截器使用,它专注于在请求过程中改变数据或行为。图2是指Mybatis拦截器的实现。责任链模式的另一种实现会阻塞请求。阻塞生成的前置条件是在处理器中自定义的,在代码中的实现比较简单。读者可以想到SpringMvc拦截器的实现流程图3.责任链模式的一种实现从代码中可以看出,在每个IHandler实现类中都会返回一个布尔返回值。如果返回的布尔值为false,责任链发起类将中断进程,其余处理器将不执行。就像我们在SpringMvc中定义的Token拦截器,如果Token失效,则无法继续访问系统,处理器会将请求回调到publicclassChainApplication{publicstaticvoidmain(String[]args){HandlerChainhandlerChain=newHandlerChain();handlerChain。addHandler(Lists.newArrayList(newHandlerA(),newHandlerB()));booleanresultFlag=handlerChain.handle();if(!resultFlag){System.out.println("责任链中的handler不满足条件");}}}读者可以在IDEA中实现两种不同的责任链模式。对比一下差异,想象一下业务中真实的应用场景,也可以运行SpringBoot项目,创建多个拦截器来支持文中的豪言壮语。图4参考SpringMvc拦截器的实现本章介绍责任链设计模式的具体语义,以及不同责任链实现类型的代码示例,并以Mybatis和SpringMvc拦截器为参考点介绍不同的代码实现和应用场景。责任链业务场景设计趁热打铁。用真实的业务场景来说明。假设业务场景是这样的,我们的系统在一个下游服务中。由于业务需求,系统中使用的基础数据需要从上游中台同步到系统数据库中。基础数据包含了很多类型的数据,虽然中台的数据会有一定的校验,但是只要是手工录入的数据,就可能会出现问题。为了遵守对上游系统不信任的原则,在接收到数据时需要进行一系列的验证。最初,需要一系列的验证原理才能入库。后来由于工期问题,只放了一套非空校验,趁着春节的时间,把这套校验规则骨架放进去。从我们系统的接入数据规则来看,个人感觉需要支持如下几套规则必填项校验,如果数据不能满足业务必填字段要求,一旦数据落入数据库,一系列的问题会出现。非法字符校验,因为我们不知道数据是怎么录入的,不知道上游系统的录入规则是什么。这些规则对于长度验证也是必需的。原因同上。如果系统中限制了某个字段的长度为50,但是传入的数据长度是500,这样也会出现问题。验证规则有两套,当然现实中可能不止这三套。但是,一旦在数据同步过程中嵌入责任链模式,将完全遵守文章开头提到的开闭原则,提高代码的可扩展性。本案例设计模式中的开闭原则是Spring支持的,后续会增加新的校验规则。您无需修改??原始代码。这里我要再次强调,设计模式的应用场景一定要灵活掌握。只有这样,对象的设计模式才能在合适的业务场景中合理使用。说完了设计模式的场景,接下来说说需要实现哪些业务需求。通过处理器链处理一批数据,返回符合要求的数据分类并不困难。定义顶层验证接口和一系列处理器实现类并不难,但是如何进行链式调用呢?这段代码需要一定的Spring基础才能看懂,我们来看看VerifyHandlerChain是如何将所有的处理器串成一条链的。VerifyHandlerChain的处理流程如下:从InitializingBean接口实现,在对应的实现方法中获取IOC容器中VerifyHandler类型的Bean,即EmptyVerifyHandler和SexyVerifyHandler。将bean添加到处理器链容器中,定义验证方法verify(),对入参数据启动对处理器链的所有调用。如果过程中没有需要校验的数据,直接返回这里使用SpringBoot工程中默认的测试类。下面测试一下如何调用@SpringBootTestclassChainApplicationTests{@AutowiredprivateVerifyHandlerChainverifyHandlerChain;@TestvoidcontextLoads(){Listverify=verifyHandlerChain.verify(Lists.newArrayList("源码兴趣圈","@龙台"));System.out.println(verify);}}这种情况下,如果客户或者产品有校验相关的需求,我们只需要实现VerifyHandler接口,新建一个校验规则实现类就可以了,符合设计模式的原则:满足开闭原则,提高代码的可扩展性。熟悉设计模式作者之前写的文章应该知道,重点是设计模式的语义,而不是具体的实现过程。那么,看我们的验证码,它结合了责任链的两种模式。上面的代码只是一个示例代码。实际业务中的实现要比这复杂得多,例如:如何定义调用处理器的顺序。比如处理器执行时间长,过滤的数据很少,所以想放在最后执行。这是过滤当前业务的所有数据类型,如何自定义对单个数据类型的过滤。比如访问学生数据,学生证有一定的校验规则。这种处理器类肯定只适合单一类型,业务场景比较多,所以设计模式应该强调一种思维,而不是固定的代码编写方式。需要结合业务场景优势灵活适配责任链模型?你必须使用责任链模型吗?不使用它能完成业务需求吗?答案是肯定的,设计模式只是有助于降低代码的复杂度,使其满足代码扩展性的开闭原则。如果不使用责任链模式,上面提到的真实同步场景会面临两个问题。如果把上面的代码逻辑校验规则写在一起,毫无疑问这个类或者方法的功能是极其庞大的。无与伦比。通常降低代码复杂度的方法是:将大块的代码逻辑拆分成函数,将大类拆分成小类是处理代码复杂度的常用方法。如果这时候说:不同的校验规则可以拆分成不同的函数,不同的类,这样是不是也能满足降低代码复杂度的要求呢?这种拆分可以解决代码复杂度,但是会面临开闭原则的第二个问题:增加新的功能应该是在现有代码的基础上扩展代码,而不是修改现有代码。想象一下,假设你写了三套验证规则并运行了一段时间。这时,领导让你加第四套。您要更改原始代码吗?综上所述,在合适的场景下使用合适的设计模式,可以降低代码设计的复杂度,变得更加健壮。更进一步还可以提高自己的编码和设计能力,告别被吐槽的烂代码……MybatisInterceptor的底层实现上面说了这么多,框架的底层源码是如何设计和使用链的责任模式?之前看Mybatis3.4.x的源码的时候了解到Interceptor的底层实现是责任链模型。这里我开门见山的给读者分享Interceptor的具体实现,直接关注Mybatis的源码。版本号3.4.7-SNAPSHOT你熟悉吗?你熟悉我们吗?上面使用的责任链模型并没有太大的不同。有处理器集拦截器,有添加处理器的方法。MybatisInterceptor不仅使用责任链,还使用动态代理服务Mybatis的四大“保护者”。在创建对象时,插件模块Para??meterHandlerResultSetHandlerStatementHandlerExecutor结合动态代理和责任链组装。用过Mybatis的读者应该知道查询SQL分页语句是使用Interceptor实现的,比如市面上的PageHelper和Mybatis-Plus分页插件。或者我们自己实现的分页插件(应该不是项目组用来显示调用多条语句形成分页)。以查询语句为例。如果定义了多个查询相关的拦截器,首先会被拦截器的代码处理。只有服务端执行完成后,才会进行真正的查询数据库操作。如果你知道如何使用它,在哪里使用它,你就会远离它。通过Interceptor也可以略知一二。想要阅读框架的源码,需要一定的设计模式基础。如果你对责任链和动态代理不清楚,那么你就无法理解这块的精髓。掌握了如何在合适的场景下使用责任链设计模式,看完文章后,你可以结合Mybatis和SpringMvc拦截器,更深入的了解责任链模式的应用场景和使用方法。另外,可以结合项目中的实际业务场景灵活使用。相信在使用之后,你会对责任链模型有更深的理解。文章参考:《设计模式之美:职责链模式》