流程介紹:
#項目是采用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本身。