流程介绍:

#项目是采用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. Js基本函数 2017-03-20

    自定义函数:  Var  y = xxx (参数) xxx. yyy():表示引用这个类的方法:如document.write() xxx.yyy: 表示引用这个类的属性: 如:atrr.lenth ...

  2. puppet客户端拉取服务端的资源时报错

    2017-11-01   16:21:47 客户端再拉取服务端的配置的资源时,出现一下报错: 造成原因:服务配置的资源不可用: 解决办法:将服务端不正确的资源配置删除: master:   cd   ...

  3. 从JavaScript的事件循环到Promise

    JS线程是单线程运行机制,就是自己按顺序做自己的事,浏览器线程用于交互和控制,JS可以操作DOM元素, 说起JS中的异步时,我们需要注意的是,JS中其实有两种异步,一种是基于浏览器的异步IO,比如Aj ...

  4. C语言第十一次作业--函数嵌套调用

    一.实验作业 1.1 PTA题目:递归法对任意10个数据按降序排序 设计思路 定义整型循环变量i,最小值下标min,中间变量t 若n==1,直接返回 否则 min=10-n 最小值下标赋初值 for ...

  5. Algorithm --> 并查集

    并查集 主要解决图的连通性问题,比如: 1.随意给你两个点,让你判断它们是否连通: 2.问你整幅图一共有几个连通分支: 初始化: void init(int size) { ; i < size ...

  6. EF Core利用Transaction对数据进行回滚保护

    What? 首先,说一下什么是EF Core中的Transaction Transaction允许以原子方式处理多个数据库操作,如果事务已提交,则所有操作都应用于数据库,如果事务回滚,则没有任何操作应 ...

  7. Java实现单向链表基本功能

    一.前言 最近在回顾数据结构与算法,有部分的算法题用到了栈的思想,说起栈又不得不说链表了.数组和链表都是线性存储结构的基础,栈和队列都是线性存储结构的应用- 本文主要讲解单链表的基础知识点,做一个简单 ...

  8. docker 学习之一:docker 安装

    核心概念1. 镜像是一个只读的模板类似于安装系统用到的那个iso文件我们通过镜像来完成各种应用的部署. 2. docker容器镜像类似于操作系统而容器类似于虚拟机本身.它可以被启动.开始.停止.删除等 ...

  9. QuietHit小Game

    根据项目的要求分别建出几个类 有游戏类 玩家类 测试类 等级类 等级时间类 一以下类图: 游戏类: public class Game { private Player player; public ...

  10. NVisionXR引擎基本介绍

    NVisionXR引擎基本介绍 一. 介绍 1.1 NVisionXR是什么?             NVisionXR引擎是全球首款跨平台多兼容的原生AR应用开发引擎,让AR应用开发更高效. 1. ...