设计模式:责任链模式的应用场景及源码应用
一、概述
多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。 可动态指定一组对象处理请求,或添加新的处理者。 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
必填项校验,如果数据无法满足业务所必须字段要求,数据一旦落入库中就会产生一系列问题 非法字符校验,因为数据如何录入,上游系统的录入规则是什么样的我们都不清楚,这一项规则也是必须的 长度校验,理由同上,如果系统某字段长度限制 50,但是接入来的数据 500长度,这也会造成问题
如果把上述说的代码逻辑校验规则写到一起,毫无疑问这个类或者说这个方法函数奇大无比。减少代码复杂性一贯方法是:将大块代码逻辑拆分成函数,将大类拆分成小类,是应对代码复杂性的常用方法。如果此时说:可以把不同的校验规则拆分成不同的函数,不同的类,这样不也可以满足减少代码复杂性的要求么。这样拆分是能解决代码复杂性,但是这样就会面临第二个问题 开闭原则:添加一个新的功能应该是,在已有代码基础上扩展代码,而非修改已有代码。大家设想一下,假设你写了三套校验规则,运行过一段时间,这时候领导让加第四套,是不是要在原有代码上改动
将请求与处理解耦。 请求处理者(节点对象)只需要关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,转发给下一个节点。 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。 链路结构灵活,可以通过改变链路的结构动态的新增或删减责任。 易于扩展新的请求处理类(节点),符合开闭原则。
责任链太长或者处理时间过长,会影响整体性能。 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。
二、入门案例
2.1 类图
2.2 基础类介绍
public interface RequestHandler {void doHandler(String req);}
public abstract class BaseRequestHandler implements RequestHandler {protected RequestHandler next;public void next(RequestHandler next) {this.next = next;}}
public class AHandler extends BaseRequestHandler {@Overridepublic void doHandler(String req) {// 处理自己的业务逻辑System.out.println("A中处理自己的逻辑");// 传递给下个类(若链路中还有下个处理类)if (next != null) {next.doHandler(req);}}}
使用类Client
public class Client {public static void main(String[] args) {BaseRequestHandler a = new AHandler();BaseRequestHandler b = new BHandler();BaseRequestHandler c = new CHandler();a.next(b);b.next(c);a.doHandler("链路待处理的数据");}}
2.3 处理流程图
三、应用场景
3.1 场景举例
场景一
对于这块业务代码,一套if-else干到底。后来,技术老大CodeReview,点名要求改掉这块。(当然,比较复杂的情况,还是可以用工作流来处理这个场景)。
场景二
具体案例
1、一切从注解开始,我这里自定义了一个注解
@Duty,这个注解有spring的@Component注解,也就是标记了这个自定义注解的类,都是交给spring的bean容器去管理。注解中,有两个属性:1.type,定义相同的type类型的bean,会被放到一个责任链集合中。2.order,同一个责任链集合中,bean的排序,数值越小,会放到链路最先的位置,优先处理。
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Servicepublic @interface Duty {/*** 标记具体业务场景* @return*/String type() default "";/*** 排序:数值越小,排序越前* @return*/int order() default 0;}
IHandler,传入2个泛型参数,供后续自定义。public interface IHandler<T, R> {/*** 抽象处理类* @param t* @return*/R handle(T t);}
HandleChainManager,用来存放不同业务下的责任链路集合。在该类中,有一个Map和两个方法。handleMap:这个map会存放责任链路中,具体的执行类,key是注解 @Duty中定义的type值,value是标记了@Duty注解的bean集合,也就是具体的执行类集合。setHandleMap:传入具体执行bean的集合,存放在map中。 executeHandle:从map中找到具体的执行bean集合,并依次执行。
public class HandleChainManager {/*** 存放责任链路上的具体处理类* k-具体业务场景名称* v-具体业务场景下的责任链路集合*/private Map<String, List<IHandler>> handleMap;/*** 存放系统中责任链具体处理类* @param handlerList*/public void setHandleMap(List<IHandler> handlerList) {handleMap = handlerList.stream().sorted(Comparator.comparingInt(h -> AnnotationUtils.findAnnotation(h.getClass(), Duty.class).order())).collect(Collectors.groupingBy(handler -> AnnotationUtils.findAnnotation(handler.getClass(), Duty.class).type()));}/*** 执行具体业务场景中的责任链集合* @param type 对应@Duty注解中的type,可以定义为具体业务场景* @param t 被执行的参数*/public <T, R> R executeHandle(String type, T t) {List<IHandler> handlers = handleMap.get(type);R r = null;if (CollectionUtil.isNotEmpty(handlers)) {for (IHandler<T, R> handler : handlers) {r = handler.handle(t);}}return r;}}
PatternConfiguration,用于装配上面的责任链管理器HandleChainManager。@Configurationpublic class PatternConfiguration {@Beanpublic HandleChainManager handlerChainExecute(List<IHandler> handlers) {HandleChainManager handleChainManager = new HandleChainManager();handleChainManager.setHandleMap(handlers);return handleChainManager;}}
SignChainHandler、EncryptionChainHandler、RequestChainHandler,这里我以SignChainHandler为例。在具体处理类上标记自定义注解
@Duty,该类会被注入到bean容器中,实现IHandler接口,只需关心自己的handle方法,处理具体的业务逻辑。@Duty(type = BusinessConstants.REQUEST, order = 1)public class SignChainHandler implements IHandler<String, String> {/*** 处理加签逻辑* @param s* @return*/@Overridepublic String handle(String s) {// 加签逻辑System.out.println("甲方爸爸要求加签");return "加签";}}
@RestController@Slf4jpublic class TestController {@Resourceprivate HandleChainManager handleChainManager;@PostMapping("/send")public String duty(@RequestBody String requestBody) {String response = handleChainManager.executeHandle(BusinessConstants.REQUEST, requestBody);return response;}}
四、源码中运用
4.1Mybatis源码中的运用
Cache,cache作为一个缓存接口,最主要的功能就是添加和获取缓存的功能,作为接口它有11个实现类,分别实现不同的功能,下面是接口源码和实现类。package org.apache.ibatis.cache;import java.util.concurrent.locks.ReadWriteLock;public interface Cache {String getId();void putObject(Object var1, Object var2);Object getObject(Object var1);Object removeObject(Object var1);void clear();int getSize();default ReadWriteLock getReadWriteLock() {return null;}}
LoggingCache的源码。主要看他的putObject方法和getObject方法,它在方法中直接传给下一个实现去执行。这个实现类其实是为了在获取缓存的时候打印缓存的命中率的。public class LoggingCache implements Cache {private final Log log;private final Cache delegate;protected int requests = 0;protected int hits = 0;public LoggingCache(Cache delegate) {this.delegate = delegate;this.log = LogFactory.getLog(this.getId());}// ...public void putObject(Object key, Object object) {this.delegate.putObject(key, object);}public Object getObject(Object key) {++this.requests;Object value = this.delegate.getObject(key);if (value != null) {++this.hits;}if (this.log.isDebugEnabled()) {this.log.debug("Cache Hit Ratio [" + this.getId() + "]: " + this.getHitRatio());}return value;}// ...}
Cache接口各种实现类的处理,最终会到达PerpetualCache这个实现类。与之前的处理类不同的是,这个类中有一个map,在map中做存取,也就是说,最终缓存还是会保存在map中的。public class PerpetualCache implements Cache {private final String id;private final Map<Object, Object> cache = new HashMap();public PerpetualCache(String id) {this.id = id;}// ...public void putObject(Object key, Object value) {this.cache.put(key, value);}public Object getObject(Object key) {return this.cache.get(key);}// ...}
4.2spring源码中的运用
4.2.1DispatcherServlet类
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new NestedServletException("Handler dispatch failed", err);}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}
4.2.2HandlerExecutionChain类
获取拦截器,执行preHandle方法
boolean applyPreHandle(HttpServletRequest request,HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = this.getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {HandlerInterceptor interceptor = interceptors[i];if (!interceptor.preHandle(request, response, this.handler)) {this.triggerAfterCompletion(request, response, (Exception)null);return false;}}}return true;}
在applyPreHandle方法中,执行triggerAfterCompletion方法
void triggerAfterCompletion(HttpServletRequest request,HttpServletResponse response, Exception ex) throws Exception {HandlerInterceptor[] interceptors = this.getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for(int i = this.interceptorIndex; i >= 0; --i) {HandlerInterceptor interceptor = interceptors[i];try {interceptor.afterCompletion(request, response, this.handler, ex);} catch (Throwable var8) {logger.error("HandlerInterceptor.afterCompletion threw exception", var8);}}}}
获取拦截器,执行applyPostHandle方法
void applyPostHandle(HttpServletRequest request,HttpServletResponse response, ModelAndView mv)throws Exception {HandlerInterceptor[] interceptors = this.getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for(int i = interceptors.length - 1; i >= 0; --i) {HandlerInterceptor interceptor = interceptors[i];interceptor.postHandle(request, response, this.handler, mv);}}}
五、代码示例
public class LeaveRequest {private String name; // 请假人姓名private int numOfDays; // 请假天数private int workingAge; //员工工龄(在公司大于2年则总经理会审批)//省略get..set..}
public abstract class ApproveHandler {protected ApproveHandler nextHandler;//下一个处理者(与类一致,这段代码很重要)public void setNextHandler(ApproveHandler approveHandler){this.nextHandler=approveHandler;}public abstract void process(LeaveRequest leaveRequest); // 处理请假(这里用了模板方法模式)}
public class PMHandler extends ApproveHandler{@Overridepublic void process(LeaveRequest leaveRequest) {//未填写姓名的请假单不通过if(null != leaveRequest.getName()){if(leaveRequest.getNumOfDays() <= 3){System.out.println(leaveRequest.getName()+",你通过项目经理审批!");}else {System.out.println("项目经理转交总经理");if(null != nextHandler){nextHandler.process(leaveRequest);}}}else {System.out.println("请假单未填写完整,未通过项目经理审批!");return;}}}
public class GMHandler extends ApproveHandler{@Overridepublic void process(LeaveRequest leaveRequest) {//员工在公司工龄超过2年,则审批通过if(leaveRequest.getWorkingAge() >=2 && leaveRequest.getNumOfDays() > 3){System.out.println(leaveRequest.getName()+",你通过总经理审批!");if(null != nextHandler){nextHandler.process(leaveRequest);}}else {System.out.println("在公司年限不够,长假未通过总经理审批!");return;}}}
public class Test {public static void main(String[] args) {PMHandler pm = new PMHandler();GMHandler gm = new GMHandler();LeaveRequest leaveRequest = new LeaveRequest();leaveRequest.setName("张三");leaveRequest.setNumOfDays(4);//请假4天leaveRequest.setWorkingAge(3);//工龄3年pm.setNextHandler(gm);//设置传递顺序pm.process(leaveRequest);}}
张三,你通过总经理审批!
六、源码中的典型应用
Netty 中的 Pipeline和ChannelHandler通过责任链设计模式来组织代码逻辑。 Spring Security 使用责任链模式,可以动态地添加或删除责任(处理 request 请求)。 Spring AOP 通过责任链模式来管理 Advisor。 Dubbo Filter 过滤器链也是用了责任链模式(链表),可以对方法调用做一些过滤处理,譬如超时(TimeoutFilter),异常(ExceptionFilter),Token(TokenFilter)等。 Mybatis 中的 Plugin 机制使用了责任链模式,配置各种官方或者自定义的 Plugin,与 Filter 类似,可以在执行 Sql 语句的时候做一些操作。 Tomcat 调用 ApplicationFilterFactory过滤器链。
spring安全框架security使用责任链模式
private static class VirtualFilterChain implements FilterChain {private final FilterChain originalChain; //链条中的节点全部执行完后,处理request请求的对象private final List<Filter> additionalFilters; //请求实际执行者,private final FirewalledRequest firewalledRequest;private final int size;private int currentPosition = 0; //链条移动的位置,当currentPosition==size,到达链条的尾端。private VirtualFilterChain(FirewalledRequest firewalledRequest,FilterChain chain, List<Filter> additionalFilters) {this.originalChain = chain;this.additionalFilters = additionalFilters;this.size = additionalFilters.size();this.firewalledRequest = firewalledRequest;}public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {if (currentPosition == size) { //到达链条尾端if (logger.isDebugEnabled()) {logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)+ " reached end of additional filter chain; proceeding with original chain");}// Deactivate path stripping as we exit the security filter chainthis.firewalledRequest.reset();originalChain.doFilter(request, response);}else {currentPosition++; //依次移动链条指针到具体节点Filter nextFilter = additionalFilters.get(currentPosition - 1);if (logger.isDebugEnabled()) {logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)+ " at position " + currentPosition + " of " + size+ " in additional filter chain; firing Filter: '"+ nextFilter.getClass().getSimpleName() + "'");}nextFilter.doFilter(request, response, this);//将链条本身的对象传递给链条成员}}}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {chain.doFilter(this.requestFactory.create((HttpServletRequest) req,(HttpServletResponse) res), res);}
七:设计模式重语意
实现自 InitializingBean 接口,在对应实现方法中获取 IOC 容器中类型为 VerifyHandler 的 Bean,也就是 EmptyVerifyHandler、SexyVerifyHandler 将 VerifyHandler 类型的 Bean 添加到处理器链容器中 定义校验方法 verify(),对入参数据展开处理器链的全部调用,如果过程中发现已无需要验证的数据,直接返回
@SpringBootTestclass ChainApplicationTests {@Autowiredprivate VerifyHandlerChain verifyHandlerChain;@Testvoid contextLoads() {List<Object> verify = verifyHandlerChain.verify(Lists.newArrayList("源码圈", "@一只阿木木"));System.out.println(verify);}}
文:一只阿木木
欢迎转发、交流小数据。
松花酿酒,春水煎茶。
眉上风止,见字如晤。
一只阿木木