【原】通過Spring重構代碼,解耦不同業務


 流程介紹:

         #項目是采用Spring Boot框架搭建的。定義了一個@Redis注解在控制層,然后當請求過來的時候會被Spring Aop攔截到對應的切面類,接着是解析相關參數拼接key調用Redis工具類查詢,如果沒有再去數據庫查詢,否則直接返回數據。

 亮點:

        #解耦依賴,獨立具體的處理器,在處理返回數據的時候需要轉換成對應的VO,例如請求的是查詢省份服務,那么返回的要轉換成List<ProvinceVo> 這種,如果是查詢市區服務,那么要轉換成List<AreaVo>,記得剛開始在代碼里是這樣寫的(偽代碼):

if(type == 1){ JsonUtil.jsonToObject(dataNode, List.class, AssociateAreasVo.class); }else if(type == 2){ JsonUtil.jsonToObject(dataNode, List.class, XXX.class); }else if(type == 3){ JsonUtil.jsonToObject(dataNode, List.class, XXX.class); }else if(type == 4){ JsonUtil.jsonToObject(dataNode, List.class, XXX.class); }else{ ........... } 

 

     #上面的代碼也不能說不好,但隨着業務的變更和需求的擴展不斷膨脹,原本是處理緩存切面的一個類瞬間耦合了一大堆不相關的代碼,維護起來非常困難,而且有開發人員經常不小心就改到其它人的代碼,導致服務不可用的情況。因此進行了重構,以避免后面不可維護性。

    重構思路:

  #由上代碼可知每個轉換數據代碼塊都是獨立的,例如省和市是屬於不同的模塊,因此把每個模塊進行拆分成不同的處理器,然后通過spring提供的api,在項目啟動的時候就把不同的處理器掃描出來,放到一個集合里面。  

 

  •  首先抽取出一個Handler接口如下:
public interface IRedisHandler {
    //匹配key
    public String handleKey(Redis redisAnno, BaseReqParam param);
    //處理轉換數據
    public Object handleReturnType(Redis redisAnno, BaseReqParam param, String content, Class clazz) throws IOException;
    //匹配處理器
    public boolean canHandle(Redis redisAnno, BaseReqParam param);
    //處理結果
    public void handleResult(Redis redisAnno, BaseReqParam param, Object result, String redisKey);
}

  

   #繼續分析,能否直接實現這個接口?答案是不行。

     原因:在緩存切面類里,我們要根據一些條件區分出選擇哪個處理器進行處理,如果直接去實現這個接口是沒有意義的(這里涉及到抽象類和接口的一個理解)。我個人理解是應該先抽取一個抽象類實現這個接口,因為在抽象類里不僅可以實現接口的所有方法,還可以編寫公共代碼,還能提供方法給子類重寫。所以有以下的 AbstractRedisHandler

 

  • 定義抽象模板:
public abstract class AbstractRedisHandler implements RedisHandler {

    private static Logger logger = Logger.getLogger(AbstractRedisHandler.class);

    @Autowired
    protected RedisService redisService;

    @Override
    public String handleKey(Redis redisAnno, BaseReqParam param) {
        return redisAnno.key();
    }

    @Override
    public Object handleReturnType(Redis redisAnno, BaseReqParam param, String content, Class clazz) throws IOException {
        return handleReturnType(redisAnno, param, content, clazz, null);
    }

    protected Object handleReturnType(Redis redisAnno, BaseReqParam param, String content, Class clazz, Class dataClass) throws IOException {
        JsonNode jsonNode = JsonUtil.parseJson(content);
        ResultVo result = getResult(jsonNode);

        if (dataClass == null) {
            dataClass = getDataClass(clazz);
            logger.info("得到數據類型:" + dataClass);
        }

        if (dataClass != null) {
            JsonNode dataNode = jsonNode.path("data");
            if (!JsonUtil.isNullNode(dataNode)) {
                Object data = JsonUtil.jsonToObject(dataNode, dataClass);
                result.setData(data);
            }
        }
        return result;
    }

    private Class getDataClass(Class clazz) {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);

            PropertyDescriptor[] arr = beanInfo.getPropertyDescriptors();
            for(PropertyDescriptor propDesc : arr) {
                String key = propDesc.getName();
                if ("data".equals(key)) {
                    Method setter = propDesc.getWriteMethod();
                    Class<?>[] classArr = setter.getParameterTypes();
                    return classArr[0];
                }
            }
        } catch (IntrospectionException e) {
            e.printStackTrace();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void handleResult(Redis redisAnno, BaseReqParam param, Object result, String redisKey) {
        try {
            if (StringUtils.isNotEmpty(redisKey)) {
                logger.info("set to redis");
                String jsonContent = JsonUtil.toJsonString(result);
                redisService.set(redisKey, jsonContent, redisAnno.expireTime());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public ResultVo getResult(JsonNode jsonNode) {
        String resultCode = null;
        String resultMsg = null;
        String errorMsg = null;
        JsonNode resultCodeNode = jsonNode.path("resultCode");
        if (!JsonUtil.isNullNode(resultCodeNode)) {
            resultCode = resultCodeNode.asText();
        }
        JsonNode resultMsgNode = jsonNode.path("resultMsg");
        if (!JsonUtil.isNullNode(resultMsgNode)) {
            resultMsg = resultMsgNode.asText();
        }
        JsonNode errorMsgNode = jsonNode.path("errorMsg");
        if (!JsonUtil.isNullNode(errorMsgNode)) {
            errorMsg = errorMsgNode.asText();
        }
        ResultVo result = new ResultVo();
        result.setResultCode(resultCode);
        result.setResultMsg(resultMsg);
        result.setErrorMsg(errorMsg);

        return result;
    }

 

  • 編寫一個業務處理器(ProvinceRedisHandler,主要實現了2個核心的方法,一個是 handleReturnType,一個是 canHandle)

   handleReturnType:具體處理邏輯

        canHandle:是否匹配,如何匹配呢?看自身業務邏輯

 

@Service
public class ProvinceRedisHandler extends AbstractRedisHandler implements RedisHandler {

    @Override
    public Object handleReturnType(Redis redisAnno, BaseReqParam param, String content, Class clazz) throws IOException {
        JsonNode jsonNode = JsonUtil.parseJson(content);
        ResultVo result = getResult(jsonNode);
        JsonNode dataNode = jsonNode.path("data");
        if (!JsonUtil.isNullNode(dataNode)) {
            List<AreaInfoVO> list = JsonUtil.jsonToObject(dataNode, List.class, AreaInfoVO.class);
            result.setData(list);
        }
        return result;
    }

    @Override
    public boolean canHandle(Redis redisAnno, BaseReqParam param) {
        if ("provinceList".equals(redisAnno.type()) && param instanceof ProvinceListReqParam) {
            return true;
        }
        return false;
    }
}

  

  最后要做的就是要如何去匹配處理器了,所以需要一個調度的類。這個類主要是將處理器封裝到一個List,然后取出@Redis注解里的type屬性,並循環List判斷當前處理器是否能匹配。

  例如@Redis(key="gateway:checkBankAccountData", type = "checkBankAccountData") ,在處理器內部判斷type是否equals "checkBankAccountData",如果是返回true,中斷循環並返回當前處理器,如果不是那么則繼續循環匹配下一個處理器。

   

定義處理器調度器:

@Service
public class RedisProcessor {
    private static Logger logger = Logger.getLogger(RedisProcessor.class);
    private List<RedisHandler> handlers;
    private boolean isInitHandlers = false;

    public String doProcessKey(Redis redisAnno, BaseReqParam param) {
        RedisHandler handler = findHandler(redisAnno, param);
        if (handler != null) {
            return handler.handleKey(redisAnno, param);
        }
        return null;
    }

//這里是處理返回的數據 public Object doProcessReturnType(Redis redisAnno, BaseReqParam param, String content, Class clazz) throws IOException { //這里是根據redisAnno和param兩個參數去匹配對應的處理器。 RedisHandler handler = findHandler(redisAnno, param); if (handler != null) {
//由於上面已經匹配到對應的處理器,這里會調用對應的處理器去處理 return handler.handleReturnType(redisAnno, param, content, clazz); } return null; } public void doProcessResult(Redis redisAnno, BaseReqParam param, Object result, String redisKey) { RedisHandler handler = findHandler(redisAnno, param); if (handler != null) { handler.handleResult(redisAnno, param, result, redisKey); } } private RedisHandler findHandler(Redis redisAnno, BaseReqParam param) { initHandlers(); if (handlers != null && handlers.size() > 0) { RedisHandler defaultRedisHandler = null; for (RedisHandler handler : handlers) { if (handler instanceof DefaultRedisHandler) { defaultRedisHandler = handler; continue; } if (handler.canHandle(redisAnno, param)) { return handler; } } if (defaultRedisHandler != null) { return defaultRedisHandler; } } return null; }

//這里是初始化handers,並把handler封裝到list,用於調度處理器匹配對應的handler。
 private synchronized void initHandlers() {
        if (!isInitHandlers) {
            handlers = SpringContextUtil.getBeanListOfType(IRedisHandler.class);
            isInitHandlers = true;
        }
    }

}

 * 注意: SpringContextUtil.getBeanListOfType 是我自己封裝的一個方法,實際內部調用的是 Spring的 getBeanOfType方法。

   解釋:getBeanOfType:獲取某一類型下的所有的bean,

  通過上面代碼可知,IRedisHandler作為一個接口,被其它處理器實現后,調用getBeanOfType便可以獲取到所有實現它的類。例如ProvinceHandlers實現了IRedisHandler,那么SpringContextUtil.getBeanListOfType便可以找出ProvinceHandlers。

  

 #繼續分析,在上面的代碼中已經拿到了所有的處理器,然后就差一件事,那就調用方要如何選擇對應的處理器進行處理呢?這時候在上面定義的RedisProcessor調度處理器就可以發揮它的用途了,將調度處理器注入到緩存切面類,使用方式如下:

@Autowired
 private RedisProcessor redisProcessor; Object result = redisProcessor.doProcessReturnType(redisAnno, baseReqParam, content, method.getReturnType());

 

#綜上所屬上面調用流程是這樣的:

    1.進入到RedisProcessor調度處理器的doProcessReturnType方法。

    2.在doProcessReturnType方法內會執行findHandler方法,根據傳過來的參數去匹配具體處理器

    3.通過匹配到的處理器就執行具體的業務操作。

    4.返回封裝好的結果給最調用方。

總結

  • 1 通過上面的重構方式不僅增加了代碼的可讀性,也減輕了維護成本。
  • 2 遵循了單一職責,即每個處理器都在做自己的事情,將不同的職責封裝在不同的類中,即將不同的變化原因封裝在不同的類中。
  • 3 spring提供的  getBeanListOfType方法方便我們去獲取某一類的所有的bean。

         通過下面源碼可知該方法返回一個map類型的實例,map中的key為bean的名字,value則是bean本身。

        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM