【原】spring BeanOfType 重构代码,遵循单一职责

  前言:最近在看一个内部网关代码的时候,发现处理Redis返回数据这块写的不错,借此时间好好研究下里面的技术点。

 

       项目流程介绍:

         #项目是采用Spring Boot框架搭建的。定义了一个@Redis注解在控制层,而后当请求过来的时候会被Spring Aop拦截到对应的切面类,接着是解析相关参数拼接key调用Redis工具类查询,若是没有再去数据库查询,不然直接返回数据。java

       亮点:

        #因为不少地方都用到了这个注解缓存,而且在处理返回数据的时候须要转换成对应的VO,例如请求的是查询省份服务,那么返回的要转换成List<ProvinceVo> 这种,若是是查询市区服务,那么要转换成List<AreaVo>,记得刚开始在代码里是这样写的(伪代码):redis

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

    重构思路:

  #由上代码可知每一个转换数据代码块都是独立的,例如省和市是属于不一样的模块,所以把每一个模块进行拆分红不一样的处理器,而后经过spring提供的api,在项目启动的时候就把不一样的处理器扫描出来,放到一个集合里面。  数据库

 

  •  首先抽取出一个Handler接口以下:
public interface IRedisHandler {
    
    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);
}

  

   #继续分析,可否直接实现这个接口?答案是不行。json

     缘由:在缓存切面类里,咱们要根据一些条件区分出选择哪一个处理器进行处理,若是直接去实现这个接口是没有意义的。应该是声明一个抽象模板类(AbstractRedisHandler)实现这个接口, 而后其它处理器再继承这个抽象模板类。api

 

  • 定义抽象模板:
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)

 

@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 是我本身封装的一个方法,实际内部调用的是 getBeanOfType。框架

   解释:getBeanOfType 顾名思义:获取某一类的全部的bean。ide

   1.该方法返回一个map类型的实例,map中的key为bean的名字,key对应的内容未bean的实例。工具

 

   2.该方法有两种类型的重载:
   getBeansOfType(Class),获取某一类的全部的bean。getBeansOfType(Class,boolean,boolean),后面两个布尔值,第一表明是否也包含原型(Class祖先)bean或者或者只是singletons(包含FactoryBean生成的),第二个表示是否当即实例化懒加载或者由FactoryBean生成的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自己。

        

相关文章
相关标签/搜索