流程介绍:

#项目是采用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本身。

JAVA通过注解处理器重构代码,遵循单一职责的更多相关文章

  1. 电商架构设计-通过系统和业务拆分,遵循单一职责原则SRP,保障整个系统的可用性和稳定性

    个人观察 1.通过系统和业务拆分,遵循单一职责原则SRP,保障整个系统的可用性和稳定性. 2.单一职责原则SRP,真的很关键,广大程序员需要不断深入理解这个原则. 3.架构图是架构师的重要输出,通过图 ...

  2. 080 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 05 单一职责原则

    080 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 05 单一职责原则 本文知识点:单一职责原则 说明:因为时间紧张,本人写博客过程中只是 ...

  3. JAVA 插入注解处理器

    JDK1.5后,Java语言提供了对注解(Annotation)的支持 JDK1.6中提供一组插件式注解处理器的标准API,可以实现API自定义注解处理器,干涉编译器的行为. 在这里,注解处理器可以看 ...

  4. java 命名代码检查-注解处理器

    命名代码检查 根据 <Java 语言规范( 第 3 版 ) > 中第6.8节的要求, Java 程序命名应当符合下列格式的书写规范: 类 ( 或接口 ) : 符合驼式命名法, 首字母大写. ...

  5. JAVA设计模式之单一职责原则

    概念: 就一个类而言应该只有一个因其他变化的原因. 流程: 问题由来:设类或接口类C负责两个不同不同的职责:职责T1,职责T2.当由于职责T1需求改变进而需要修改类C时,可能导致职责T2收到不可预知的 ...

  6. Java的注解总结

    java 1.5开始引入了注解和反射,正确的来说注解是反射的一部分,没有反射,注解无法正常使用,但离开注解,反射依旧可以使用.Java的注解详解和自定义注解: https://blog.csdn.ne ...

  7. AS 注解处理器 APT Processor MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  8. 软件开发中的单一职责(转至INFOQ)

    最近在实践微服务化过程中,对其“单一职责”原则深有体会.那么只有微服务化才可以单一职责,才可以解耦吗?答案是否定的. 单一职责原则是这样定义的:单一的功能,并且完全封装起来. 我们做后端Java开发的 ...

  9. 1.单一职责原则(Single Responsibility Principle)

    1.定义 就一个类而言,应该仅有一个引起它变化的原因. 2.定义解读 这是六大原则中最简单的一种,通俗点说,就是不存在多个原因使得一个类发生变化,也就是一个类只负责一种职责的工作. 3.优点 类的复杂 ...

随机推荐

  1. Android Studio报错:the selected directory is not a valid home for unknow sdk

    今天在使用Android Studio的时候不知道怎么了,没有import module,视图里面也没有android视图,查看project设置.提示我的SDK路径无效:the selected d ...

  2. python函数式编程之yield表达式形式

    先来看一个例子 def foo(): print("starting...") while True: res = yield print("res:",res ...

  3. R语言-用户细分

    案例:通过使用R语言的聚类算法将用户进行合理的划分,找出对超市贡献度,光临度最高的优质客户,对后期的推广有更深远的影响 1.导入包 library(dplyr) library(reshape2) l ...

  4. 关于Maven的配置与学习

    1. 简介 官方说法:Apache Maven is a software project management and comprehension tool. Based on the concep ...

  5. Matplotlib库的使用

    *可通过dpi修改输出质量 plot函数 第一种方法会使图中所有的字体改变,而第二种方法只会改变中文字体,推荐使用第二种方法.

  6. 基于jQuery/zepto的单页应用(SPA)搭建方案

    这里介绍一个基于jquery或zepto的单页面应用方案,遵循尽可能简单的原则,使大家一目了然,只需配置一个路由,之后完全按照jq日常写法即可完成.可做学习使用,也可修改后用于一些业务逻辑简单的spa ...

  7. 排序算法Java实现(希尔排序)

    算法描述:先将待排序序列的数组元素分成多个子序列,使得每个子序列的元素个数相对较少,然后对各个子序列分别进行直接插入排序,待整个待排序序列“基本有序”后,再对所有元素进行一次直接插入排序. packa ...

  8. 【Python】 文件目录比较工具filecmp和difflib

    在一些运维场景中,常常需要比较两个环境中的应用目录结构(是否有文件/目录层面上的增删)以及比较两个环境中同名文件内容的不同(即文件层面上的改).Python自带了两个内建模块可以很好地完成这个工作,f ...

  9. MyAdapter Andriod

    private List<T> listdate;//定义数据对象 //为了获取item中的点击事件定义ViewHolderprivate static class ViewHolder ...

  10. JavaScript(第十八天)【DOM基础】

    学习要点: 1.DOM介绍 2.查找元素 3.DOM节点 4.节点操作 DOM(Document Object Model)即文档对象模型,针对HTML和XML文档的API(应用程序接口).DOM描绘 ...