责任链设计模式与应用:实现拦截器责任链统计接口执行时间和性能

一、什么是责任链设计模式

1、什么是责任链设计模式?

责任链设计模式主要构成有抽象处理者、具体处理者、客户类,在处理请求的时候,将请求通过客户类发送至处理链路上,这样链路上所有处理对象都有机会处理请求,使发送者与接收者之间解耦。

见:https://blog.csdn.net/noaman_wgs/article/details/77838222

2、使用场景

使用责任链模式的优点:

  • 发送者与接收方的处理对象类之间解耦;
  • 封装每个处理对象,符合类的最小封装原则;
  • 可以任意添加处理对象,调整处理对象之间的顺序,提高了维护性和可拓展性;

使用场景:

当请求到来时,不知道由哪个具体对象去处理或者每个对象都需要处理请求的时候,可以使用责任链模式。比如在OA系统中,当员工发起一个流程的时候,需要经过HR-组长-部门老大-公司老总等审批的时候,就可以使用责任链模式,不同的处理对象(HR-组长-部门老大-公司老总)使用不同的封装类组装成一个处理链路,使用这个处理链路去处理员工发起的请求。

3、责任链模式与策略模式的区别

策略模式和责任链模式很像,都是有多个处理对象去处理同一个请求。

不同之处在于对于同一个请求,策略模式可以经过选择使用具体的策略类处理请求;而责任链模式不能根据请求判断使用哪个处理类处理请求,需要链路上的处理类全部处理一遍请求才能得出结果。

二、代码实战

代码:https://github.com/nomico271/inspire-demo/tree/master/Ch1_ExecutorChainPattern

下面将结合Spring AOP知识,应用责任链设计模式,设计一个能够打印调用接口的参数、统计执行时间、执行次数的拦截器。

用到的知识点有:

  • 框架:Spring Boot
  • 设计模式:责任链设计模式
  • Spring AOP
  • 反射
  • 注解

项目结构:

在这里插入图片描述

示意图如下:

在这里插入图片描述

下面看下代码实现。

1、拦截器责任链的实现

(1)首先定义拦截器抽象:可以定义拦截器的顺序、方法执行前的逻辑和方法执行完的逻辑,见InspireInterceptor.java;

(2)实现自定义功能的具体拦截器的实现,如打印接口入参信息的拦截器、统计接口调用执行时间的拦截器、统计接口调用次数的拦截器等等,见:

  • CostTimeInterceptor.java
  • LoggerInterceptor.java
  • MethodCountInterceptor.java

(3)拦截器客户端,构建拦截器执行链并进行排序,之后按顺序执行拦截器链路中拦截器的before和after方法,见InspireInterceptorChainClient.java

对责任链进行排序的代码如下:

    @PostConstruct
    public void loadInterceptors() {
    
    
        if (!CollectionUtils.isEmpty(interceptorList)) {
    
    
            for (InspireInterceptor interceptor : interceptorList) {
    
    
                interceptor.setOrder(resolveOrder(interceptor));
            }

            Collections.sort(interceptorList, (o1, o2) -> o1.getOrder() - o2.getOrder());
        }
    }

    /**
     * 获取拦截器注解中定义的优先级
     *
     * @param interceptor
     * @return
     */
    private int resolveOrder(InspireInterceptor interceptor) {
    
    
        if (!interceptor.getClass().isAnnotationPresent(InterceptorOrder.class)) {
    
    
            return InterceptorOrder.LOWEST_ORDER;
        } else {
    
    
            return interceptor.getClass().getAnnotation(InterceptorOrder.class).order();
        }
    }

(4)另:在拦截器执行过程中,可以自定义拦截器执行顺序,见注解@InterceptorOrder;

同时,需要有个上下文保存调用的方法获取接口入参等信息,以便能够在多个拦截器中传递,见:InspireContext、InspireRequest、InspireResponse、TheadLocalHolder

完整代码如下:

// 注解类, 定义拦截器顺序
@Retention(RetentionPolicy.RUNTIME)
@Target({
    
    ElementType.TYPE})
@Documented
public @interface InterceptorOrder {
    
    

    /**
     * 优先级, 值越小, 优先级越高
     *
     * @return
     */
    int order() default LOWEST_ORDER ;

    int LOWEST_ORDER = Integer.MAX_VALUE;
    int HIGHEST_ORDER = Integer.MIN_VALUE;
}




// 拦截器
public abstract class InspireInterceptor {
    
    

    private int order;

    public int getOrder() {
    
    
        return order;
    }
    public void setOrder(int order) {
    
    
        this.order = order;
    }
    public boolean executeBefore(InspireContext context) {
    
    
        return true;
    }
    public void executeAfter(InspireContext context) {
    
    }
}



//  拦截器实现1: 打印接口调用参数
@Slf4j
@Component
@InterceptorOrder(order = 10)
public class LoggerInterceptor extends InspireInterceptor {
    
    

    @Override
    public boolean executeBefore(InspireContext context) {
    
    
        return super.executeBefore(context);
    }

    @Override
    public void executeAfter(InspireContext context) {
    
    
        String method = context.getRequest().getMethod();
        Map<String, Object> paramsMap = context.getRequest().getParamsMap();
        log.info("invoke [method:{}] start, params:{}", method, paramsMap.toString());
    }
}


// 拦截器实现2 - 统计接口调用次数
@Slf4j
@Component
@InterceptorOrder(order = 20)
public class MethodCountInterceptor extends InspireInterceptor {
    
    

    // 统计方法调用次数
    private static final Map<String, Integer> methodCountMap = new ConcurrentHashMap<>();


    // 统计方法调用次数
    private static final Map<String, Integer> methodSuccessCountMap = new ConcurrentHashMap<>();

    // 统计方法调用失败次数
    private static final Map<String, Integer> methodFailCountMap = new ConcurrentHashMap<>();



    @Override
    public boolean executeBefore(InspireContext context) {
    
    
        return true;
    }

    @Override
    public void executeAfter(InspireContext context) {
    
    
        log.info("[MethodCountInterceptor] execute after");
        RpcResult result = (RpcResult) context.getResponse().getData();
        String method = context.getRequest().getMethod();

        setMethodCount(method, methodCountMap);
        if (result.isSuccess()) {
    
    
            setMethodCount(method, methodSuccessCountMap);
        } else {
    
    
            setMethodCount(method, methodFailCountMap);
        }

        log.info("invoke method:[{}], success times:{}, fail times:{}, total times:{}", method,
                getMethodCount(method, methodSuccessCountMap), getMethodCount(method, methodFailCountMap), getMethodCount(method, methodCountMap));
        
    }

    private void setMethodCount(String key, final Map<String, Integer> map) {
    
    
        if (map.get(key) == null) {
    
    
            map.put(key, 1);
        } else {
    
    
            map.put(key, map.get(key) + 1);
        }
    }

    private int getMethodCount(String key, Map<String, Integer> map) {
    
    
        if (map.get(key) == null) {
    
    
            return 0;
        }
        return map.get(key);
    }
}


// // 拦截器实现3 - 统计接口执行时间
@Slf4j
@Component
@InterceptorOrder(order = 30)
public class CostTimeInterceptor extends InspireInterceptor {
    
    

    private long start;
    private long end;

    @Override
    public boolean executeBefore(InspireContext context) {
    
    
        start = System.currentTimeMillis();
        return true;
    }

    @Override
    public void executeAfter(InspireContext context) {
    
    
        end = System.currentTimeMillis();
        log.info("invoke method:{} costTime:{} ms, result:{}", context.getRequest().getMethod(), (end - start), context.getResponse().getData());
    }
}


// 拦截器客户端:初始化拦截器建成拦截器责任链,对拦截器进行排序,并按顺序依次执行拦截器链
@Component
public class InspireInterceptorChainClient {
    
    

    @Autowired
    private List<InspireInterceptor> interceptorList;


    @PostConstruct
    public void loadInterceptors() {
    
    
        if (!CollectionUtils.isEmpty(interceptorList)) {
    
    
            for (InspireInterceptor interceptor : interceptorList) {
    
    
                interceptor.setOrder(resolveOrder(interceptor));
            }

            Collections.sort(interceptorList, (o1, o2) -> o1.getOrder() - o2.getOrder());
        }
    }

    /**
     * 获取拦截器注解中定义的优先级
     *
     * @param interceptor
     * @return
     */
    private int resolveOrder(InspireInterceptor interceptor) {
    
    
        if (!interceptor.getClass().isAnnotationPresent(InterceptorOrder.class)) {
    
    
            return InterceptorOrder.LOWEST_ORDER;
        } else {
    
    
            return interceptor.getClass().getAnnotation(InterceptorOrder.class).order();
        }
    }

    public boolean processBefore(InspireContext context) {
    
    
        for (InspireInterceptor interceptor : interceptorList) {
    
    
            boolean isPass = interceptor.executeBefore(context);
            if (!isPass) {
    
    
                return isPass;
            }
        }
        return true;
    }

    public void processAfter(InspireContext context) {
    
    
        for (InspireInterceptor interceptor : interceptorList) {
    
    
            interceptor.executeAfter(context);
        }
    }

}


// 上下文类:InspireContext、InspireRequest、InspireResponse、TheadLocalHolder
// 拦截器上下文类:InspireContext
@Data
public final class InspireContext {
    
    

    private InspireRequest request;
    private InspireResponse response;

    public InspireContext(InspireRequest request, InspireResponse response) {
    
    
        this.request = request;
        this.response = response;
    }

    public static void setContext(InspireContext context) {
    
    
        TheadLocalHolder.setInspireContext(context);
    }

    public static InspireContext getContext() {
    
    
        return TheadLocalHolder.getInspireContext();
    }

    public static void removeContext() {
    
    
        TheadLocalHolder.removeInspireContext();
    }
}

// 方法请求参数
@Data
public class InspireRequest {
    
    

    private String method;
    private Map<String, Object> paramsMap;
}

// 方法执行结果
@Data
public class InspireResponse<T> {
    
    
    private T data;
}

// 保存当前线程信息
public class TheadLocalHolder {
    
    

    private static final ThreadLocal<InspireContext> LOCAL_CONTEXT= new ThreadLocal() ;

    public static void setInspireContext(InspireContext context){
    
    
        LOCAL_CONTEXT.set(context) ;
    }

    public static void removeInspireContext(){
    
    
        LOCAL_CONTEXT.remove();
    }

    public static InspireContext getInspireContext(){
    
    
        return LOCAL_CONTEXT.get() ;
    }

}





2、切面类:在调用接口方法前后执行拦截器

完整代码如下:

@Component
@Aspect
public class MethodAspect {
    
    

    @Autowired
    private InspireInterceptorChainClient interceptorClient;


    @Pointcut("execution(* com.wgs.inspire.设计模式.demo2.责任链.test.service.*.*(..))")
    public void doLogPointCut(){
    
    }

    @Around("doLogPointCut()")
    public Object doAfterReturning(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        System.out.println("====invoke aop start====");

        // build inspireContext
        InspireContext inspireContext = buildInspireContext(joinPoint);

        // execute interceptorChain
        interceptorClient.processBefore(inspireContext);
        RpcResult result = (RpcResult) joinPoint.proceed();
        inspireContext.getResponse().setData(result);
        interceptorClient.processAfter(inspireContext);

        System.out.println("====invoke aop end====");

        return result;
    }

    private InspireContext buildInspireContext(ProceedingJoinPoint joinPoint) {
    
    
        InspireRequest request = new InspireRequest();
        request.setMethod(joinPoint.getSignature().getName());

        Map<String, Object> map = new HashMap<>();
        // 参数名
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] paramNames = methodSignature.getParameterNames();

        // 参数对应的值
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < paramNames.length; i++) {
    
    
            map.put(paramNames[i], args[i]);
        }
        request.setParamsMap(map);

        InspireResponse response = new InspireResponse();
        InspireContext inspireContext = new InspireContext(request, response);

        return inspireContext;
    }
}

需要配置AOP的使用:
application.properties:

spring.aop.auto=true

3、测试部分

注意:为了在切面类中获取接口调用结果统一,需要定义好接口返回值的包装类,此处用的是RpcResult以及对应构建器RpcResultBuilder。

测试的完整代码如下:

// 为了方便解析接口请求结果,使用RpcResult统一返回结果
@Data
public class RpcResult<T> implements Serializable{
    
    

    private String code;

    private boolean success;

    private String msg;

    private T data;
}


public class RpcResultBuilder {
    
    

    public static <T> RpcResult<T> buildSuccess(T data) {
    
    
        RpcResult<T> result = new RpcResult();
        result.setSuccess(Boolean.TRUE);
        result.setCode("200");
        result.setMsg("success");
        result.setData(data);
        return result;
    }

    public static <T> RpcResult<T> buildError(String code, String errorMsg) {
    
    
        RpcResult<T> result = new RpcResult();
        result.setSuccess(Boolean.FALSE);
        result.setCode(code);
        result.setMsg(errorMsg);
        return result;
    }
}



//  接口
public interface RefundService {
    
    

    RpcResult<String> refund(String order, String operator);

    RpcResult<Boolean> refuseRefund(String orderId, String reason);
}

@Service
public class RefundServiceImpl implements RefundService {
    
    

    @Override
    public RpcResult<String> refund(String order, String operator) {
    
    

        try {
    
    
            // 模拟接口执行时间
            try {
    
    
                Thread.sleep(1500);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("Refund order: " + order);
            return RpcResultBuilder.buildSuccess("success");
        } catch (Exception e) {
    
    
            return RpcResultBuilder.buildError("400", "false");
        }

    }

    @Override
    public RpcResult<Boolean> refuseRefund(String orderId, String reason) {
    
    
        try {
    
    
            // 模拟接口执行时间
            try {
    
    
                Thread.sleep(1200);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

            // 模拟出错
            int orderNum = Integer.valueOf(orderId);
            if (orderNum == 0) {
    
    
                int mockError = orderNum / 0 ;
            }


            System.out.println("Refuse Refund order: " + orderId + ", reason : " + reason);
            return RpcResultBuilder.buildSuccess(true);
        } catch (Exception e) {
    
    
            return RpcResultBuilder.buildError("400", "false");
        }
    }
  
  
  
  // 测试类
  @Controller
public class TestController {
    
    

    @Autowired
    private RefundService refundService;

    @RequestMapping("/test/refund")
    @ResponseBody
    public void testService(@RequestParam("orderId") String order,
                            @RequestParam("operator")String operator) {
    
    
        refundService.refund(order, operator);
    }

    @RequestMapping("/test/refuseRefund")
    @ResponseBody
    public void testRefuseRefundService(@RequestParam("orderId") String order,
                            @RequestParam("reason")String reason) {
    
    
        refundService.refuseRefund(order, reason);
    }
}

4、测试结果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nqfm9Lmr-1572709782503)(/Users/wanggenshen/Library/Application%20Support/typora-user-images/image-20191102002331820.png)]

注:以上代码未在生产环境中使用,仅提供一个思路,部分代码还需要斟酌修改。


参考:https://crossoverjie.top/2018/10/22/wheel/cicada5/
代码:https://github.com/nomico271/inspire-demo/tree/master/Ch1_ExecutorChainPattern

猜你喜欢

转载自blog.csdn.net/noaman_wgs/article/details/102878166