流程介绍:

#项目是采用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. Cesium解决按住滚轮旋转时进入地下的问题

    viewer.clock.onTick.addEventListener(function () {       setMinCamera()})  var setMinCamera = functi ...

  2. java安全框架shiro(一)

    第一个简单的案例 ,通过读取.ini文件的方式模拟登陆, 1.通过Factory工厂的getInstance()方法来获取SecurityManager的实例,实例化Factory需要一个ini文件的 ...

  3. 笔记:Hibernate 二级缓存

    Hibernate 包括二个级别的缓存,默认的总是启用Session级别的一级缓存,可选的 SessionFactory 级别的二级缓存,Session级别的一级缓存,但应用保存持久化实体.修改持久化 ...

  4. django初探-创建简单的博客系统

    django第一步 1. django安装 pip install django print(django.get_version()) 查看django版本 2. 创建项目 打开cmd,进入指定目录 ...

  5. 初识mango DB

    换工作了,第一次接触到mango数据库,有点云里雾里,整理一篇最基本的增删该查语句 百度百科说mango DB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据 ...

  6. c++ --> 重载、重写(覆盖)和隐藏的区别

    重载.重写(覆盖)和隐藏的区别 一.重载 重载从overload翻译过来,是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心 ...

  7. New UWP Community Toolkit - XAML Brushes

    概述 上一篇 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾.接下来会针对每个重要更新,结合 SDK 源代码和调用代码详细讲解. 本篇我们 ...

  8. 基于php编写的新闻类爬虫,插入WordPress数据库

    这个爬虫写的比较久远,很久没有更新博客了. 1.首先思路是:通过php的curl_setopt()函数可以方便快捷的抓取网页. 2.什么样的新闻吸引人呢,当然的热点新闻了.这里选百度的搜索风云榜,获取 ...

  9. uboot中的命令体系

    一.uboot的命令体系介绍以及实例分析: U-Boot 的命令实现大多在 common 目录下.在该目录下命令的代码文件都是以“ cmd_”开头的,如下图所示: 其中每一个文件都是一个命令实现的代码 ...

  10. 极其简单的帮你理解ORM中的关联关系

    ORM对象关系映射(英语:(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转 ...