4

聊一聊责任链模式_wx630f055ce23fc的技术博客_51CTO博客

 1 year ago
source link: https://blog.51cto.com/u_15773567/5819272
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

责任链模式(Chain of Responsibility Pattern)是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止,属于行为型模式。 下面放一张足球比赛的图,通过层层传递,最终射门。通过这张图,可以更好的理解责任链模式。

聊一聊责任链模式_缓存

二、入门案例

2.1 类图

聊一聊责任链模式_链路_02

2.2 基础类介绍

抽象接口RequestHandler

/**
* @author 往事如风
* @version 1.0
* @date 2022/10/25 13:41
* @description
*/
public interface RequestHandler {

void doHandler(String req);
}
复制代码

抽象类BaseRequestHandler

/**
* @author 往事如风
* @version 1.0
* @date 2022/10/25 13:45
* @description
*/
public abstract class BaseRequestHandler implements RequestHandler {

protected RequestHandler next;

public void next(RequestHandler next) {
this.next = next;
}
}
复制代码

具体处理类AHandler

/**
* @author 往事如风
* @version 1.0
* @date 2022/10/25 14:00
* @description
*/
public class AHandler extends BaseRequestHandler {

@Override
public void doHandler(String req) {
// 处理自己的业务逻辑
System.out.println("A中处理自己的逻辑");
// 传递给下个类(若链路中还有下个处理类)
if (next != null) {
next.doHandler(req);
}
}
}

复制代码

当然还有具体的处理类B、C等等,这里不展开赘述。 使用类Client

/**
* @author 往事如风
* @version 1.0
* @date 2022/10/25 14:06
* @description
*/
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 处理流程图

聊一聊责任链模式_缓存_03

三、应用场景

3.1 场景举例

前两年,在一家金融公司待过一段时间,其中就有一个业务场景:一笔订单进来,会先在后台通过初审人员进行审批,初审不通过,订单流程结束。初审通过以后,会转给终审人员进行审批,不通过,流程结束;通过,流转到下个业务场景。 对于这块业务代码,之前一代目是一个叫知了的同事,他撸起袖子就是干,一套if-else干到底。后来,技术老大CodeReview,点名要求改掉这块。于是乎,想到用用设计模式吧,然后就噼里啪啦一顿改。(当然,比较复杂的情况,还是可以用工作流来处理这个场景,当时碍于时间成本,也就放弃了)。

上家公司对接甲方爸爸的时候,对方会调用我们接口,将数据同步过来。同样,我们需要将处理好的数据,传给他们。由于双方传输数据都是加密传输,所以在接受他们数据之前,需要对数据进行解密,验签,参数校验等操作。同样,我们给他们传数据也需要进行加签,加密操作。

话不多说,对于场景二,我来放一些伪代码,跟大家一起探讨下。 1、一切从注解开始,我这里自定义了一个注解​​@Duty​​,这个注解有spring的​​@Component​​注解,也就是标记了这个自定义注解的类,都是交给spring的bean容器去管理。 注解中,有两个属性:1.type,定义相同的type类型的bean,会被放到一个责任链集合中。2.order,同一个责任链集合中,bean的排序,数值越小,会放到链路最先的位置,优先处理。

/**
* @author 往事如风
* @version 1.0
* @date 2022/10/25 16:11
* @description
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Service
public @interface Duty {
/**
* 标记具体业务场景
* @return
*/
String type() default "";

/**
* 排序:数值越小,排序越前
* @return
*/
int order() default 0;
}
复制代码

2、定义一个顶层的抽象接口​​IHandler​​,传入2个泛型参数,供后续自定义。

/**
* @author 往事如风
* @version 1.0
* @date 2022/10/25 15:31
* @description 责任链顶层抽象类
*/
public interface IHandler<T, R> {
/**
* 抽象处理类
* @param t
* @return
*/
R handle(T t);
}

3、定义一个责任链bean的管理类​​HandleChainManager​​,用来存放不同业务下的责任链路集合。在该类中,有一个Map和两个方法。

  1. handleMap:这个map会存放责任链路中,具体的执行类,key是注解​​@Duty​​中定义的type值,value是标记了​​@Duty​​注解的bean集合,也就是具体的执行类集合。
  2. setHandleMap:传入具体执行bean的集合,存放在map中。
  3. executeHandle:从map中找到具体的执行bean集合,并依次执行。
/**
* @author 往事如风
* @version 1.0
* @date 2022/10/25 16:00
* @description 责任链管理类
*/
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;
}
}

4、定义一个配置类​​PatternConfiguration​​,用于装配上面的责任链管理器​​HandleChainManager​​。

/**
* @author 往事如风
* @version 1.0
* @date 2022/10/25 15:35
* @description 设计模式配置类
*/
@Configuration
public class PatternConfiguration {

@Bean
public HandleChainManager handlerChainExecute(List<IHandler> handlers) {
HandleChainManager handleChainManager = new HandleChainManager();
handleChainManager.setHandleMap(handlers);
return handleChainManager;
}

}

5、具体的处理类:​​SignChainHandler​​、​​EncryptionChainHandler​​、​​RequestChainHandler​​,这里我以​​SignChainHandler​​为例。 在具体处理类上标记自定义注解​​@Duty​​,该类会被注入到bean容器中,实现​​IHandler​​接口,只需关心自己的handle方法,处理具体的业务逻辑。

/**
* @author 往事如风
* @version 1.0
* @date 2022/10/25 15:31
* @description 加签类
*/
@Duty(type = BusinessConstants.REQUEST, order = 1)
public class SignChainHandler implements IHandler<String, String> {
/**
* 处理加签逻辑
* @param s
* @return
*/
@Override
public String handle(String s) {
// 加签逻辑
System.out.println("甲方爸爸要求加签");
return "加签";
}
}

6、具体怎么调用?这里我写了个测试controller直接调用,具体如下:

/**
* @author 往事如风
* @version 1.0
* @date 2022/9/6 17:32
* @description
*/
@RestController
@Slf4j
public class TestController {

@Resource
private HandleChainManager handleChainManager;

@PostMapping("/send")
public String duty(@RequestBody String requestBody) {
String response = handleChainManager.executeHandle(BusinessConstants.REQUEST, requestBody);
return response;
}
}

7、执行结果,会按照注解中标记的order依次执行。

聊一聊责任链模式_sed_04

至此,完工。又可以开心的撸代码了,然后在具体的执行类中,又是一顿if-else。。。

四、源码中运用

4.1Mybatis源码中的运用

Mybatis中的缓存接口​​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;
}
}
聊一聊责任链模式_链路_05

下面,我们来看下其中一个子类​​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类

DispatcherServlet 核心方法 doDispatch。HandlerExecutionChain只是维护HandlerInterceptor的集合,可以向其中注册相应的拦截器,本身不直接处理请求,将请求分配给责任链上注册处理器执行,降低职责链本身与处理逻辑之间的耦合程度。

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 afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

4.2.2HandlerExecutionChain类

这里分析的几个方法,都是从DispatcherServlet类的doDispatch方法中请求的。

  • 获取拦截器,执行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);
}
}
}

5.1 优点

  1. 将请求与处理解耦。
  2. 请求处理者(节点对象)只需要关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,转发给下一个节点。
  3. 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。
  4. 链路结构灵活,可以通过改变链路的结构动态的新增或删减责任。
  5. 易于扩展新的请求处理类(节点),符合开闭原则

5.2 缺点

  1. 责任链太长或者处理时间过长,会影响整体性能。
  2. 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。

六、参考源码

编程文档:
https://gitee.com/cicadasmile/butte-java-note

应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK