JAVA通过注解处理器重构代码,遵循单一职责
流程介绍:
#项目是采用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通过注解处理器重构代码,遵循单一职责的更多相关文章
- 电商架构设计-通过系统和业务拆分,遵循单一职责原则SRP,保障整个系统的可用性和稳定性
个人观察 1.通过系统和业务拆分,遵循单一职责原则SRP,保障整个系统的可用性和稳定性. 2.单一职责原则SRP,真的很关键,广大程序员需要不断深入理解这个原则. 3.架构图是架构师的重要输出,通过图 ...
- 080 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 05 单一职责原则
080 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 05 单一职责原则 本文知识点:单一职责原则 说明:因为时间紧张,本人写博客过程中只是 ...
- JAVA 插入注解处理器
JDK1.5后,Java语言提供了对注解(Annotation)的支持 JDK1.6中提供一组插件式注解处理器的标准API,可以实现API自定义注解处理器,干涉编译器的行为. 在这里,注解处理器可以看 ...
- java 命名代码检查-注解处理器
命名代码检查 根据 <Java 语言规范( 第 3 版 ) > 中第6.8节的要求, Java 程序命名应当符合下列格式的书写规范: 类 ( 或接口 ) : 符合驼式命名法, 首字母大写. ...
- JAVA设计模式之单一职责原则
概念: 就一个类而言应该只有一个因其他变化的原因. 流程: 问题由来:设类或接口类C负责两个不同不同的职责:职责T1,职责T2.当由于职责T1需求改变进而需要修改类C时,可能导致职责T2收到不可预知的 ...
- Java的注解总结
java 1.5开始引入了注解和反射,正确的来说注解是反射的一部分,没有反射,注解无法正常使用,但离开注解,反射依旧可以使用.Java的注解详解和自定义注解: https://blog.csdn.ne ...
- AS 注解处理器 APT Processor MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- 软件开发中的单一职责(转至INFOQ)
最近在实践微服务化过程中,对其“单一职责”原则深有体会.那么只有微服务化才可以单一职责,才可以解耦吗?答案是否定的. 单一职责原则是这样定义的:单一的功能,并且完全封装起来. 我们做后端Java开发的 ...
- 1.单一职责原则(Single Responsibility Principle)
1.定义 就一个类而言,应该仅有一个引起它变化的原因. 2.定义解读 这是六大原则中最简单的一种,通俗点说,就是不存在多个原因使得一个类发生变化,也就是一个类只负责一种职责的工作. 3.优点 类的复杂 ...
随机推荐
- 学习ASP.NET Core Razor 编程系列二——添加一个实体
在Razor页面应用程序中添加一个实体 在本篇文章中,学习添加用于管理数据库中的书籍的实体类.通过实体框架(EF Core)使用这些类来处理数据库.EF Core是一个对象关系映射(ORM)框架,它简 ...
- 如何在IPFS里面上传一张图片
之前有好几人问过小编,想在IPFS里面上传一张图片.如何做? 今天小编就讲一下如何在IPFS里面上传.下载文件? 1 下载IPFS软件 下载地址:https://dist.ipfs.io/#go-ip ...
- 如何写对kubernetes的模板文件
kubernetes的模板配置文件随着版本更迭也会有相应的调整,正确配置模板关键字的方式是参考版本发布的doc,如下图 在docs\api-reference下面有不同功能的API目录,如下图 各个A ...
- freeMark的入门教程
1.FreeMarker支持如下转义字符: \";双引号(u0022) \';单引号(u0027) \\;反斜杠(u005C) \n;换行(u000A) \r;回车(u000D) \t;Ta ...
- ~psd面试 求最长回文序列 DP求解
链接:https://www.nowcoder.com/acm/contest/90/D来源:牛客网 掌握未来命运的女神 psd 师兄在拿了朝田诗乃的 buff 后决定去实习. 埃森哲公司注册成立于爱 ...
- Java实现单向链表反转
public class LinkedListTest { public static void main(String[] args) { Node A = new Node("A&quo ...
- (Matlab)GPU计算所需的配置
电脑:联想扬天 M4400 系统:win 7 X64 硬件:NVIDIA GeForce GT 740M 独显2G 硬件驱动: 软件: Matlab 2015a %需要安装 Paralle ...
- [bzoj1565][NOI2009]植物大战僵尸_网络流_拓扑排序
植物大战僵尸 bzoj1565 题目大意:给你一张网格图,上面种着一些植物.你从网格的最右侧开始进攻.每个植物可以对僵尸提供能量或者消耗僵尸的能量.每个植物可以保护一个特定网格内的植物,如果一个植物被 ...
- 安装php扩展 ffmpeg-php
环境: CentOS 6.5 PHP5.6 安装前php 已加载GD 模块(yum install php-gd)1.添加ffmpeg和ffmpeg-devel源 cat > /etc/yum. ...
- JVM学习八:常用JVM配置参数
前面学习的都是和类加载相关的知识,接下来学习的则和GC相关的知识,都是JVM的几个重点块. 零.在IDE的后台打印GC日志: 既然学习JVM,阅读GC日志是处理Java虚拟机内存问题的基础技能,它只是 ...