一、介绍
什么是责任链模式?(Chain of Responsibility Pattern),简单的说,为请求者和接受者之间创建一条对象处理链路,避免请求发送者与接收者耦合在一起!
例如,如下图:
从设计的角度看,责任链模式涉及到四个角色:
- 请求角色:可以是外部的请求或者内部的请求,最终体现就是一个请求数据体;
- 抽象处理器角色:定义处理的一些基本的规范;
- 具体处理器角色:实现或者继承抽象处理器,完成具体的计算任务;
- 接着角色:用于接受请求数据最终的处理结果;
下面我们一起来看看具体的实际应用!
二、示例
在实际开发中,经常避免不了会与其他公司进行接口对接,绝大部分请求参数都是经过加密处理再发送到互联网上,下面我们以对请求参数进行验证、封装处理为例,来诠释责任链模式的玩法,实现过程如下!
- 我们先编写一个加密工具类,采用AES加密算法
public class AESUtil { private static Logger log = LoggerFactory.getLogger(AESUtil.class); private static final String AES = "AES"; private static final String AES_CVC_PKC = "AES/CBC/PKCS7Padding"; static { Security.addProvider(new BouncyCastleProvider()); } /** * 加密 * @param content * @param key * @return * @throws Exception */ public static String encrypt(String content, String key) { try { SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), AES); Cipher cipher = Cipher.getInstance(AES_CVC_PKC); IvParameterSpec iv = new IvParameterSpec(new byte[16]); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv); byte[] encrypted = cipher.doFinal(content.getBytes()); return Base64.getEncoder().encodeToString(encrypted); } catch (Exception e) { log.warn("AES加密失败,参数:{},错误信息:{}", content, ExceptionUtils.getStackTrace(e)); return ""; } } /** * 解密 * @param content * @param key * @return * @throws Exception */ public static String decrypt(String content, String key) { try { SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), AES); Cipher cipher = Cipher.getInstance(AES_CVC_PKC); IvParameterSpec iv = new IvParameterSpec(new byte[16]); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv); byte[] encrypted = Base64.getDecoder().decode(content); byte[] original = cipher.doFinal(encrypted); return new String(original, "UTF-8"); } catch (Exception e) { log.warn("AES解密失败,参数:{},错误信息:{}", content, ExceptionUtils.getStackTrace(e)); return ""; } } public static void main(String[] args) throws Exception { String key = "1234567890123456"; String content = "{\"userCode\":\"zhangsan\",\"userPwd\":\"123456\"}"; String encryptContext = encrypt(content, "1234567890123456"); System.out.println("加密后的内容:" + encryptContext); String decryptContext = decrypt(encryptContext, key); System.out.println("解密后的内容:" + decryptContext); } }
执行结果如下:
加密后的内容:5ELORDsYKxCz6Ec377udct7dBMI74ZtJDCFL4B3cpoBsPC8ILH/aiaRFnZa/oTC5 解密后的内容:{"userCode":"zhangsan","userPwd":"123456"}
其中加密后的内容可以看作为请求者传过来的参数!
- 同时,再创建一个上下文实体类
ServiceContext
,用于数据记录
/** * 上下文 */ public class ServiceContext { /** * 请求参数 */ private String requestParam; /** * 解密后的数据 */ private String jsonData; /** * 用户账号 */ private String userCode; /** * 用户密码 */ private String userPwd; //省略set\get public ServiceContext() { } public ServiceContext(String requestParam) { this.requestParam = requestParam; } }
- 然后,创建一个处理器接口
HandleIntercept
public interface HandleIntercept { /** * 对参数进行处理 * @param context * @return */ ServiceContext handle(ServiceContext context); }
- 紧接着,创建两个处理器实现类,用于参数解密、业务数据验证
/** * 解密请求数据 */ public class DecodeDataHandle implements HandleIntercept { private String key = "1234567890123456"; @Override public ServiceContext handle(ServiceContext context) { String jsonData = AESUtil.decrypt(context.getRequestParam(), key); if(StringUtils.isEmpty(jsonData)){ throw new IllegalArgumentException("解密失败"); } context.setJsonData(jsonData); return context; } }
/** * 验证业务数据并封装 */ public class ValidDataHandle implements HandleIntercept { @Override public ServiceContext handle(ServiceContext context) { String jsonData = context.getJsonData(); JSONObject jsonObject = JSONObject.parseObject(jsonData); if(!jsonObject.containsKey("userCode")){ throw new IllegalArgumentException("userCode不能为空"); } context.setUserCode(jsonObject.getString("userCode")); if(!jsonObject.containsKey("userPwd")){ throw new IllegalArgumentException("userPwd不能为空"); } context.setUserPwd(jsonObject.getString("userPwd")); return context; } }
- 最后创建一个处理链路管理器
HandleChain
/** * 请求处理链路管理器 */ public class HandleChain { private List<HandleIntercept> handleInterceptList = new ArrayList<>(); /** * 添加处理器 * @param handleIntercept */ public void addHandle(HandleIntercept handleIntercept){ handleInterceptList.add(handleIntercept); } /** * 执行处理 * @param context * @return */ public ServiceContext execute(ServiceContext context){ if(!handleInterceptList.isEmpty()){ for (HandleIntercept handleIntercept : handleInterceptList) { context =handleIntercept.handle(context); } } return context; } }
- 写完之后,我们编写一个测试类
ChainClientTest
public class ChainClientTest { public static void main(String[] args) { //获取请求参数 String requestParam = "5ELORDsYKxCz6Ec377udct7dBMI74ZtJDCFL4B3cpoBsPC8ILH/aiaRFnZa/oTC5"; //封装请求参数 ServiceContext serviceContext = new ServiceContext(requestParam); //添加处理链路 HandleChain handleChain = new HandleChain(); handleChain.addHandle(new DecodeDataHandle());//解密处理 handleChain.addHandle(new ValidDataHandle());//数据验证处理 //执行处理链,获取处理结果 serviceContext = handleChain.execute(serviceContext); System.out.println("处理结果:" + JSONObject.toJSONString(serviceContext)); } }
执行之后结果如下:
处理结果:{"jsonData":"{\"userCode\":\"zhangsan\",\"userPwd\":\"123456
可以很清晰的看到,从请求者发送数据经过处理器链路之后,数据都封装到上下文中去了!
如果想继续验证用户和密码是否合法,可以继续添加新的处理器,即可完成数据的处理验证!
如果是传统的方法,可能就是多个if
,进行嵌套,类似如下:
if(condition){ if(condition){ if(condition){ //业务处理 } } }
这种模式,最大的弊端就是可读性非常差,而且代码不好维护!
而责任链是从接口层进行封装处理和判断,可扩展性非常强!
三、应用
责任链模式的使用场景,这个就不多说了,最典型的就是 Servlet 中的 Filter,有了上面的分析,大家应该也可以理解 Servlet 中责任链模式的工作原理了,然后为什么一个一个的 Filter 需要配置在 web.xml 中,其实本质就是将 filter 注册到处理器中。
public class TestFilter implements Filter{ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); } public void destroy() {} public void init(FilterConfig filterConfig) throws ServletException {} }
四、总结
既然责任链模式这么好用,那什么时候用责任链模式?
在系统设计的时候,如果每个 if 都有一个统一的抽象,例如参数加密、系统数据验证、业务参数验证等等处理,可以将其抽象,使用对象处理进行链式调用,不仅实现优雅,而且易复用可扩展。