巧妙的運(yùn)用責(zé)任鏈模式,讓你的代碼高出一個(gè)逼格!
本文轉(zhuǎn)載自微信公眾號「Java極客技術(shù)」,作者鴨血粉絲。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號。
一、介紹
什么是責(zé)任鏈模式?(Chain of Responsibility Pattern),簡單的說,為請求者和接受者之間創(chuàng)建一條對象處理鏈路,避免請求發(fā)送者與接收者耦合在一起!
例如,如下圖:
從設(shè)計(jì)的角度看,責(zé)任鏈模式涉及到四個(gè)角色:
- 請求角色:可以是外部的請求或者內(nèi)部的請求,最終體現(xiàn)就是一個(gè)請求數(shù)據(jù)體;
- 抽象處理器角色:定義處理的一些基本的規(guī)范;
- 具體處理器角色:實(shí)現(xiàn)或者繼承抽象處理器,完成具體的計(jì)算任務(wù);
- 接著角色:用于接受請求數(shù)據(jù)最終的處理結(jié)果;
下面我們一起來看看具體的實(shí)際應(yīng)用!
二、示例
在實(shí)際開發(fā)中,經(jīng)常避免不了會與其他公司進(jìn)行接口對接,絕大部分請求參數(shù)都是經(jīng)過加密處理再發(fā)送到互聯(lián)網(wǎng)上,下面我們以對請求參數(shù)進(jìn)行驗(yàn)證、封裝處理為例,來詮釋責(zé)任鏈模式的玩法,實(shí)現(xiàn)過程如下!
我們先編寫一個(gè)加密工具類,采用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加密失敗,參數(shù):{},錯(cuò)誤信息:{}", 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解密失敗,參數(shù):{},錯(cuò)誤信息:{}", 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("加密后的內(nèi)容:" + encryptContext);
- String decryptContext = decrypt(encryptContext, key);
- System.out.println("解密后的內(nèi)容:" + decryptContext);
- }
- }
執(zhí)行結(jié)果如下:
- 加密后的內(nèi)容:5ELORDsYKxCz6Ec377udct7dBMI74ZtJDCFL4B3cpoBsPC8ILH/aiaRFnZa/oTC5
- 解密后的內(nèi)容:{"userCode":"zhangsan","userPwd":"123456"}
其中加密后的內(nèi)容可以看作為請求者傳過來的參數(shù)!
- 同時(shí),再創(chuàng)建一個(gè)上下文實(shí)體類ServiceContext,用于數(shù)據(jù)記錄
- /**
- * 上下文
- */
- public class ServiceContext {
- /**
- * 請求參數(shù)
- */
- private String requestParam;
- /**
- * 解密后的數(shù)據(jù)
- */
- private String jsonData;
- /**
- * 用戶賬號
- */
- private String userCode;
- /**
- * 用戶密碼
- */
- private String userPwd;
- //省略set\get
- public ServiceContext() {
- }
- public ServiceContext(String requestParam) {
- this.requestParam = requestParam;
- }
- }
- 然后,創(chuàng)建一個(gè)處理器接口HandleIntercept
- public interface HandleIntercept {
- /**
- * 對參數(shù)進(jìn)行處理
- * @param context
- * @return
- */
- ServiceContext handle(ServiceContext context);
- }
- 緊接著,創(chuàng)建兩個(gè)處理器實(shí)現(xiàn)類,用于參數(shù)解密、業(yè)務(wù)數(shù)據(jù)驗(yàn)證
- /**
- * 解密請求數(shù)據(jù)
- */
- 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;
- }
- }
- /**
- * 驗(yàn)證業(yè)務(wù)數(shù)據(jù)并封裝
- */
- 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;
- }
- }
最后創(chuàng)建一個(gè)處理鏈路管理器HandleChain
- /**
- * 請求處理鏈路管理器
- */
- public class HandleChain {
- private List<HandleIntercept> handleInterceptList = new ArrayList<>();
- /**
- * 添加處理器
- * @param handleIntercept
- */
- public void addHandle(HandleIntercept handleIntercept){
- handleInterceptList.add(handleIntercept);
- }
- /**
- * 執(zhí)行處理
- * @param context
- * @return
- */
- public ServiceContext execute(ServiceContext context){
- if(!handleInterceptList.isEmpty()){
- for (HandleIntercept handleIntercept : handleInterceptList) {
- context =handleIntercept.handle(context);
- }
- }
- return context;
- }
- }
寫完之后,我們編寫一個(gè)測試類ChainClientTest
- public class ChainClientTest {
- public static void main(String[] args) {
- //獲取請求參數(shù)
- String requestParam = "5ELORDsYKxCz6Ec377udct7dBMI74ZtJDCFL4B3cpoBsPC8ILH/aiaRFnZa/oTC5";
- //封裝請求參數(shù)
- ServiceContext serviceContext = new ServiceContext(requestParam);
- //添加處理鏈路
- HandleChain handleChain = new HandleChain();
- handleChain.addHandle(new DecodeDataHandle());//解密處理
- handleChain.addHandle(new ValidDataHandle());//數(shù)據(jù)驗(yàn)證處理
- //執(zhí)行處理鏈,獲取處理結(jié)果
- serviceContext = handleChain.execute(serviceContext);
- System.out.println("處理結(jié)果:" + JSONObject.toJSONString(serviceContext));
- }
- }
執(zhí)行之后結(jié)果如下:
- 處理結(jié)果:{"jsonData":"{\"userCode\":\"zhangsan\",\"userPwd\":\"123456\"}","requestParam":"5ELORDsYKxCz6Ec377udct7dBMI74ZtJDCFL4B3cpoBsPC8ILH/aiaRFnZa/oTC5","userCode":"zhangsan","userPwd":"123456"}
可以很清晰的看到,從請求者發(fā)送數(shù)據(jù)經(jīng)過處理器鏈路之后,數(shù)據(jù)都封裝到上下文中去了!
如果想繼續(xù)驗(yàn)證用戶和密碼是否合法,可以繼續(xù)添加新的處理器,即可完成數(shù)據(jù)的處理驗(yàn)證!
如果是傳統(tǒng)的方法,可能就是多個(gè)if,進(jìn)行嵌套,類似如下:
- if(condition){
- if(condition){
- if(condition){
- //業(yè)務(wù)處理
- }
- }
- }
這種模式,最大的弊端就是可讀性非常差,而且代碼不好維護(hù)!
而責(zé)任鏈?zhǔn)菑慕涌趯舆M(jìn)行封裝處理和判斷,可擴(kuò)展性非常強(qiáng)!
三、應(yīng)用
責(zé)任鏈模式的使用場景,這個(gè)就不多說了,最典型的就是 Servlet 中的 Filter,有了上面的分析,大家應(yīng)該也可以理解 Servlet 中責(zé)任鏈模式的工作原理了,然后為什么一個(gè)一個(gè)的 Filter 需要配置在 web.xml 中,其實(shí)本質(zhì)就是將 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 {}
- }
四、總結(jié)
既然責(zé)任鏈模式這么好用,那什么時(shí)候用責(zé)任鏈模式?
在系統(tǒng)設(shè)計(jì)的時(shí)候,如果每個(gè) if 都有一個(gè)統(tǒng)一的抽象,例如參數(shù)加密、系統(tǒng)數(shù)據(jù)驗(yàn)證、業(yè)務(wù)參數(shù)驗(yàn)證等等處理,可以將其抽象,使用對象處理進(jìn)行鏈?zhǔn)秸{(diào)用,不僅實(shí)現(xiàn)優(yōu)雅,而且易復(fù)用可擴(kuò)展。
五、參考
1、五月的倉頡 - 責(zé)任鏈模式