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