【原】spring BeanOfType 重構代碼,遵循單一職責

  前言:最近在看一個內部網關代碼的時候,發現處理Redis返回數據這塊寫的不錯,藉此時間好好研究下里面的技術點。

 

       項目流程介紹:

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

       亮點:

        #因爲不少地方都用到了這個註解緩存,而且在處理返回數據的時候須要轉換成對應的VO,例如請求的是查詢省份服務,那麼返回的要轉換成List<ProvinceVo> 這種,若是是查詢市區服務,那麼要轉換成List<AreaVo>,記得剛開始在代碼裏是這樣寫的(僞代碼):redis

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

    重構思路:

  #由上代碼可知每一個轉換數據代碼塊都是獨立的,例如省和市是屬於不一樣的模塊,所以把每一個模塊進行拆分紅不一樣的處理器,而後經過spring提供的api,在項目啓動的時候就把不一樣的處理器掃描出來,放到一個集合裏面。  數據庫

 

  •  首先抽取出一個Handler接口以下:
public interface IRedisHandler {
    
    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);
}

  

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

     緣由:在緩存切面類裏,咱們要根據一些條件區分出選擇哪一個處理器進行處理,若是直接去實現這個接口是沒有意義的。應該是聲明一個抽象模板類(AbstractRedisHandler)實現這個接口, 而後其它處理器再繼承這個抽象模板類。api

 

  • 定義抽象模板:
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)

 

@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 是我本身封裝的一個方法,實際內部調用的是 getBeanOfType。框架

   解釋:getBeanOfType 顧名思義:獲取某一類的全部的bean。ide

   1.該方法返回一個map類型的實例,map中的key爲bean的名字,key對應的內容未bean的實例。工具

 

   2.該方法有兩種類型的重載:
   getBeansOfType(Class),獲取某一類的全部的bean。getBeansOfType(Class,boolean,boolean),後面兩個布爾值,第一表明是否也包含原型(Class祖先)bean或者或者只是singletons(包含FactoryBean生成的),第二個表示是否當即實例化懶加載或者由FactoryBean生成的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自己。

        

相關文章
相關標籤/搜索