zoukankan      html  css  js  c++  java
  • 【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

  • 相关阅读:
    解决ListView异步加载数据之后不能点击的问题
    android点击实现图片放大缩小 java技术博客
    关于 数据文件自增长 的一点理解
    RAC 实例不能启动 ORA1589 signalled during ALTER DATABASE OPEN
    Linux 超级用户的权利
    RAC 实例 迁移到 单实例 使用导出导入
    Shell 基本语法
    Linux 开机引导与关机过程
    RAC 实例不能启动 ORA1589 signalled during ALTER DATABASE OPEN
    Oracle RAC + Data Guard 环境搭建
  • 原文地址:https://www.cnblogs.com/heyang78/p/15494101.html
Copyright © 2011-2022 走看看