【Design Pattern】将职责链模式应用到Rest服务中去

笔者工作中制备过一个Rest服务,其中有预处理、服务中止验证、黑名单验证、实际数据返回四个模块,写在一起显得庞杂冗长,维护难度较大。

受公众号“捡田螺的小男孩”文章“实战!工作中用到哪些设计模式”一文中职责链部分的启发,将服务代码改写一遍。

改完后觉得这次该写还是挺有效的,各块权责分明了,还能有效糅合在一起,故撰文记之。

首先需要制备一个handler抽象类,作为四种处理的基类。

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

public abstract class FutureHandler {
    private FutureHandler nextHandler;

    public void setNextHandler(FutureHandler nextHandler) {
        this.nextHandler = nextHandler;
    }
    
    public FutureHandler getNextHandler() {
        return nextHandler;
    }

    public boolean filter(HttpServletRequest rqst,Map<String,Object> map) {
        boolean result=doFilter(rqst,map);
        
        if(result==true) {
            if(nextHandler!=null) {
                return nextHandler.filter(rqst,map);
            }else {
                return true;
            }
        }else {
            return false;
        }
    }
    
    abstract boolean doFilter(HttpServletRequest rqst,Map<String,Object> map);
}

这个类的作用是处理正确就往下nexthandler传, filter函数是精髓所在,它成功的把事做完往下传递的逻辑和各自做事分离开来。

然后是四个子类:

第一个类:预处理类

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.hy.myapp.util.RestUtil;

@Component
@Order(0)
public class PreHandler extends FutureHandler{
    @Override
    boolean doFilter(HttpServletRequest rqst,Map<String, Object> map) {
        long startMs=System.currentTimeMillis();
        map.put("startMs", startMs);
        
        final String CODE="8974";
        map.put("Interface ID", CODE);
        String startTime=LocalDate.now()+" "+LocalTime.now();
        map.put("startTime", startTime);
        String visitorIp=RestUtil.findVisitorIpFrom(rqst);
        map.put("visitorIp", visitorIp);

        return true;
    }

}

这个类摆在第一位,主要是取时间取IP等,不会出现不成功的情况,于是末尾直接返回true。

中断处理器类:

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.hy.myapp.service.FutureService;
import com.hy.myapp.util.DateTimeUtil;

@Component
@Order(1)
public class InterruptHandler extends FutureHandler{
    @Autowired
    private FutureService ftService;
    
    @Override
    boolean doFilter(HttpServletRequest rqst,Map<String, Object> map) {
        String code=map.get("Interface ID")+"";
        
        if(ftService.isCodeDisabled(code)==true) {
            map.put("code", "301");
            map.put("msg", "该ID代表的服务已经被中止使用");
            
            String endTime=LocalDate.now()+" "+LocalTime.now();
            map.put("endTime", endTime);
            
            long startMs=Long.parseLong(map.get("startMs")+"");
            long endMs=System.currentTimeMillis();
            map.put("timeElapsed", DateTimeUtil.ms2DHMS(startMs,endMs)+"");
            
            return false;
        }

        return true;
    }

}

这个类摆在第二位,主要检查某服务code是否被禁用,在数据库里有一张表来决定code的disabled状态是1还是0.

黑名单类:

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.hy.myapp.service.FutureService;
import com.hy.myapp.util.DateTimeUtil;

@Component
@Order(2)
public class BlacksheetHandler extends FutureHandler{
    @Autowired
    private FutureService ftService;
    
    @Override
    boolean doFilter(HttpServletRequest rqst,Map<String, Object> map) {
        String code=map.get("Interface ID")+"";
        String ip=map.get("visitorIp")+"";
        
        if(ftService.isIpBlocked(code,ip)==true) {
            map.put("code", "302");
            map.put("msg", "您的IP在黑名单中,进一步访问已被中止。");
            
            String endTime=LocalDate.now()+" "+LocalTime.now();
            map.put("endTime", endTime);
            
            long startMs=Long.parseLong(map.get("startMs")+"");
            long endMs=System.currentTimeMillis();
            map.put("timeElapsed", DateTimeUtil.ms2DHMS(startMs,endMs)+"");
            
            return false;
        }

        return true;
    }

}

这个类摆在第三位,主要职责是看请求IP是否在黑名单中,在数据库中有一张黑名单表,如果服务code和ip能查到的记录数大于0,则说明请求ip在黑名单。

最后就是实际处理类:

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.hy.myapp.util.DateTimeUtil;

class VarietyInfo {
    // 名称
    private String name;
    
    // 交易单位
    private String unit;
    
    // 振幅
    private String amplitude;
    
    public VarietyInfo(String name,String unit,String amplitude) {
        this.name=name;
        this.unit=unit;
        this.amplitude=amplitude;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUnit() {
        return unit;
    }

    public void setUnit(String unit) {
        this.unit = unit;
    }

    public String getAmplitude() {
        return amplitude;
    }

    public void setAmplitude(String amplitude) {
        this.amplitude = amplitude;
    }
}

@Component
@Order(3)
public class VarietyHandler extends FutureHandler{
    @Override
    boolean doFilter(HttpServletRequest rqst,Map<String, Object> map) {
        map.put("code", "303");
        map.put("msg", "顺利取得数据");
        
        List<VarietyInfo> datas=new ArrayList<VarietyInfo>();
        datas.add(new VarietyInfo("黄大豆/A",    "10吨/手",    "1元/吨"));
        datas.add(new VarietyInfo("大豆二号/B",    "10吨/手",    "1元/吨"));
        datas.add(new VarietyInfo("豆粕/M",        "10吨/手",    "1元/吨"));
        datas.add(new VarietyInfo("豆油/Y",        "10吨/手",    "2元/吨"));
        datas.add(new VarietyInfo("玉米/C",        "10吨/手",    "1元/吨"));
        datas.add(new VarietyInfo("玉米淀粉/CS",    "10吨/手",    "1元/吨"));
        datas.add(new VarietyInfo("聚乙烯/L",    "5吨/手",    "5元/吨"));
        datas.add(new VarietyInfo("棕榈油/P",    "10吨/手",    "2元/吨"));
        datas.add(new VarietyInfo("聚氯乙烯/V",    "5吨/手",    "5元/吨"));
        datas.add(new VarietyInfo("冶金焦炭/J",    "100吨/手",    "0.5元/吨"));
        datas.add(new VarietyInfo("焦煤/JM",        "60吨/手",    "0.5元/吨"));
        datas.add(new VarietyInfo("铁矿石/I",    "100吨/手",    "0.5元/吨"));
        datas.add(new VarietyInfo("鲜鸡蛋/JD",    "5吨/手",    "1元/500千克"));
        datas.add(new VarietyInfo("中密度纤维板/FB","500吨/手","0.05元/张"));
        datas.add(new VarietyInfo("细木工板/BB",    "500吨/手",    "0.05元/张"));
        datas.add(new VarietyInfo("聚丙烯/PP",    "5吨/手",    "1元/吨"));
        datas.add(new VarietyInfo("粳米/RR",        "10吨/手",    "1元/吨"));
        datas.add(new VarietyInfo("苯乙烯/EB",    "5吨/手",    "1元/吨"));
        
        map.put("data", datas);
            
        String endTime=LocalDate.now()+" "+LocalTime.now();
        map.put("endTime", endTime);
        
        long startMs=Long.parseLong(map.get("startMs")+"");
        long endMs=System.currentTimeMillis();
        map.put("timeElapsed", DateTimeUtil.ms2DHMS(startMs,endMs)+"");

        return true;
    }

}

这个类是返回实际数据,任务到此完成。

然后,将四个类注入进来。

@RestController
public class FutureRestCtrl {
    @Autowired
    private List<FutureHandler> hdlList;
    
...
}

Component能决定四个类的示例放到hdlList里,order决定了放入顺序,故hdlList启动后就初始化完毕了。

在请求初,需要初始化handler和后继handler:

@RestController
public class FutureRestCtrl {
    @Autowired
    private List<FutureHandler> hdlList;
    
    private FutureHandler handler;
    
    private void initHandlerChain() {
        for(int i=0;i<hdlList.size();i++) {
            if(i==0) {
                handler=hdlList.get(0);
            }else {
                FutureHandler curr=hdlList.get(i-1);
                FutureHandler next=hdlList.get(i);
                curr.setNextHandler(next);
            }
        }
    }
....
}

initHandlerChain的作用就是把四个类的示例首尾串起来,而handler取第一个。

四个类工作起来是这样的:

        while(handler!=null) {
            boolean result=handler.doFilter(rqst,retvalMap);
            if(result==false) {
                return retvalMap;
            }else {
                handler=handler.getNextHandler();
            }
        }

原作中多个类依次处理,没有返回值和异常处理,这里和原作不同,如果返回值是false就没必要往下了,故做了一个循环判断,而返回值是true就继续往下,直到职责链末端为止。

最终的Rest类如下,可见比原来是苗条多了:

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * 期货Rest服务
 * @author hy
 * 2021年11月1日
 */
@RestController
public class FutureRestCtrl {
    @Autowired
    private List<FutureHandler> hdlList;
    
    private FutureHandler handler;
    
    private void initHandlerChain() {
        for(int i=0;i<hdlList.size();i++) {
            if(i==0) {
                handler=hdlList.get(0);
            }else {
                FutureHandler curr=hdlList.get(i-1);
                FutureHandler next=hdlList.get(i);
                curr.setNextHandler(next);
            }
        }
    }
    
    @RequestMapping(value="/tradingVarieties", method=RequestMethod.GET)
    public Map<String,Object> getVarieties(HttpServletRequest rqst) {
        initHandlerChain();
        Map<String,Object> retvalMap=new LinkedHashMap<>();
        
        while(handler!=null) {
            boolean result=handler.doFilter(rqst,retvalMap);
            if(result==false) {
                return retvalMap;
            }else {
                handler=handler.getNextHandler();
            }
        }
        
        return retvalMap;
    }
}

以下各种情况出现的返回的json结果说明了职责链作业的正确性。

服务被中止时:

{
    "Interface ID": "8974",
    "startTime": "2021-11-01 13:20:47.219432900",
    "visitorIp": "0:0:0:0:0:0:0:1",
    "code": "301",
    "msg": "该ID代表的服务已经被中止使用",
    "endTime": "2021-11-01 13:20:47.225423600",
    "timeElapsed": "6ms"
}

访问IP在黑名单中时:

{
    "Interface ID": "8974",
    "startTime": "2021-11-01 13:37:51.289498300",
    "visitorIp": "0:0:0:0:0:0:0:1",
    "code": "302",
    "msg": "您的IP在黑名单中,进一步访问已被中止。",
    "endTime": "2021-11-01 13:37:51.750911700",
    "timeElapsed": "462ms"
}

返回正确的结果时:

{
    "Interface ID": "8974",
    "startTime": "2021-11-01 13:21:33.431997800",
    "visitorIp": "0:0:0:0:0:0:0:1",
    "code": "303",
    "msg": "顺利取得数据",
    "data": [
        {
            "name": "黄大豆/A",
            "unit": "10吨/手",
            "amplitude": "1元/吨"
        },
        {
            "name": "大豆二号/B",
            "unit": "10吨/手",
            "amplitude": "1元/吨"
        },
        {
            "name": "豆粕/M",
            "unit": "10吨/手",
            "amplitude": "1元/吨"
        },
        {
            "name": "豆油/Y",
            "unit": "10吨/手",
            "amplitude": "2元/吨"
        },
        {
            "name": "玉米/C",
            "unit": "10吨/手",
            "amplitude": "1元/吨"
        },
        {
            "name": "玉米淀粉/CS",
            "unit": "10吨/手",
            "amplitude": "1元/吨"
        },
        {
            "name": "聚乙烯/L",
            "unit": "5吨/手",
            "amplitude": "5元/吨"
        },
        {
            "name": "棕榈油/P",
            "unit": "10吨/手",
            "amplitude": "2元/吨"
        },
        {
            "name": "聚氯乙烯/V",
            "unit": "5吨/手",
            "amplitude": "5元/吨"
        },
        {
            "name": "冶金焦炭/J",
            "unit": "100吨/手",
            "amplitude": "0.5元/吨"
        },
        {
            "name": "焦煤/JM",
            "unit": "60吨/手",
            "amplitude": "0.5元/吨"
        },
        {
            "name": "铁矿石/I",
            "unit": "100吨/手",
            "amplitude": "0.5元/吨"
        },
        {
            "name": "鲜鸡蛋/JD",
            "unit": "5吨/手",
            "amplitude": "1元/500千克"
        },
        {
            "name": "中密度纤维板/FB",
            "unit": "500吨/手",
            "amplitude": "0.05元/张"
        },
        {
            "name": "细木工板/BB",
            "unit": "500吨/手",
            "amplitude": "0.05元/张"
        },
        {
            "name": "聚丙烯/PP",
            "unit": "5吨/手",
            "amplitude": "1元/吨"
        },
        {
            "name": "粳米/RR",
            "unit": "10吨/手",
            "amplitude": "1元/吨"
        },
        {
            "name": "苯乙烯/EB",
            "unit": "5吨/手",
            "amplitude": "1元/吨"
        }
    ],
    "endTime": "2021-11-01 13:21:33.437013300",
    "timeElapsed": "6ms"
}

好了,到这里就要结束了。以上职责链模式如果优雅,部分功劳拜Component和order两个Spring标签所致;如果体会不到上述模式的优雅,那就是您自己的问题了,懂得都懂,无需赘述。上面不足的地方是四个子类及其父类函数参数一致,实际上rqst只给第一个类用了,对于其它三个类是纯摆设,这也是一种代码浪费吧,这个问题留待日后看有没有办法解决。

END

原文地址:https://www.cnblogs.com/heyang78/p/15494101.html