• 电商项目之Java8函数式接口落地实践


    在这里插入图片描述

    1 问题背景

    在电商场景中,会调用很多第三方的云服务,比如发送邮件、发起支付、发送验证码等等。由于网络存在抖动,有时候发起调用后会拿到500的状态码,io exception等报错,因此需要重新调用,简称重试机制。项目中很多地方用到重试机制,导致很多重复的代码,因此笔者考虑使用Java8函数式接口优化该重试机制,抽成一个工具类方法。

    2 前言

    1. 本文的代码中,可能有些类型没有给出代码,不需要纠结,主要了解函数式接口怎么应用即可

    3 多处重复的重试机制代码

    项目中多次出现的代码如下:

            BasicResponse<String> response = null;
            int retryTimes = 0;
            do {
                try {
                    String startTimeStr = DATE_TIME_FORMATTER.format(LocalDateTime.now());
                    response = restTemplate.postForString(basicRequest); // 此行代码是可变的,可能是get方式请求,可能是post方式
                    String endTimeStr = DATE_TIME_FORMATTER.format(LocalDateTime.now());
                    PayReq logObject = PayReq.getLogObject(payReq);
                    log.info("XXXPay payOrder, request:{}, response:{}, startTimeStr:{}, endTimeStr:{}, retryTimes:{}", JSON.toJSONString(logObject), JSON.toJSONString(response), startTimeStr, endTimeStr, retryTimes);
                } finally {
                    if (response != null && !response.getCode().equals(HttpStatus.SC_OK)) {
                        try {
                            Thread.sleep(500L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    retryTimes++;
                }
            } while (!response.getCode().equals(HttpStatus.SC_OK) && retryTimes < 3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    分析:

    如上所示,在这行代码response = restTemplate.postForString(basicRequest);是可变的,有可能是get方式提交http请求,有可能是post方式。因此要把此处抽象出来,交给调用者写具体实现。调用者需要拿到http响应报文,那么抽象出来的接口,需要有返回值。那么此处可以使用Supplier函数式接口,或者自己定义一个有返回值的函数式接口也可以。

    log.info打日志这行,需要打出响应报文、开始时间、结束时间、重试次数等,这些都可以抽到工具类里面,但是日志的内容XXXPay payOrder这些是可变的,应该交由调用者写具体实现。那么我们可以定义一个函数式接口出来,有入参但无返回值,入参是提供给调用者使用的。

    4 优化后的代码

    定义一个打日志的函数式接口:

    /**
     * 打日志的函数式接口
     * 
     * @param 
     */
    @FunctionalInterface
    public interface LogFunc<T> {
    
        /**
         * 打日志
         * 
         * @param response 响应报文
         * @param startTimeStr http调用开始时间
         * @param endTimeStr http调用结束时间
         * @param curTime 当前重试次数
         */
        void log(T response,  String startTimeStr, String endTimeStr, int curTime);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    Http重试工具类如下,主要关注有代码注释的那两处地方即可:

    @Slf4j
    public class HttpRetryUtil {
        private final static DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS");
    
        public static <T> T retryOnException(Supplier<T> supplier, LogFunc logFunc,int maxRetryTimes, long sleepMillis) {
            T result = null;
            int retryTimes = 0;
            do {
                try {
                    String startTimeStr = LocalDateTime.now().format(DATE_TIME_FORMATTER);
                    // 交给调用者写具体实现,并把值返回出去
                    result = supplier.get();
                    String endTimeStr = LocalDateTime.now().format(DATE_TIME_FORMATTER);
                    // 交给调用者写具体实现,入参供调用者使用
                    logFunc.log(result, startTimeStr, endTimeStr, retryTimes);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (result != null && !((BasicResponse<String>) result).getCode().equals(HttpStatus.SC_OK)) {
                        try {
                            Thread.sleep(sleepMillis);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    retryTimes++;
                }
            } while (((result == null) || !((BasicResponse<String>) result).getCode().equals(HttpStatus.SC_OK))
                    && retryTimes < maxRetryTimes);
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    测试用例,如下所示,优化前有21行/代码(见第3小节的代码),其实如果不写注释不换行,只需用1行就可以将这个重试机制调用起来了(见下面的代码),简洁多了:

    @Slf4j
    public class HttpRetryUtilTest extends AppTest {
    
        @Resource
        private HttpRestTemplate restTemplate;
    
        @Test
        public void testRetry(){
            BasicRequest basicRequest = new BasicRequest();
            basicRequest.setMethodUrl("https://www.google.com");
    
            BasicResponse<String> resp = HttpRetryUtil.retryOnException(
                    // 实现supplier函数式接口
                    () -> restTemplate.getForString(basicRequest), 
                    // 实现LogFunc函数式接口
                    (response, startTimeStr, endTimeStr, curTime) 
                            -> log.info("HttpRetryUtil retryOnException, request:{}, response:{}, startTimeStr:{}, endTimeStr:{}, times:{}", JSON.toJSONString(basicRequest), JSON.toJSONString(response), startTimeStr, endTimeStr, curTime), 
                    3, 500L);
    
            log.info("repsonse:{}", JSON.toJSONString(resp));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    5 进一步优化

    针对那些重试次数、休眠时间,可以在工具类中再定义一些默认的重试次数、默认的休眠时间,然后利用Java的多态特性(方法重载)定义多种工具方法即可。

  • 相关阅读:
    【SQL学习】常用命令
    面试失败的反思:如何从错误中吸取教训
    Python垃圾回收和GC模块
    [极客大挑战 2019]RCE ME 取反绕过正则匹配 绕过disable_function设置
    计算机开题报告怎么写?开题报告的几大模块!研究内容、研究方法、工作安排、预期目的
    Java的HTTPClient工具类(附代码实现)
    Java代码基础算法练习-求数据序列的最大值及最小值---2024.3.15
    Flink基于时间窗口定时输出到ElasticSearch中并做到真正不丢数据
    【LeetCode】单词规律
    【校招VIP】数据库之隔离级别相关
  • 原文地址:https://blog.csdn.net/qq_40634846/article/details/134322638