zoukankan      html  css  js  c++  java
  • springBoot中怎么减少if---else,怎么动态手动注册类进入Spring容器

    由于业务中经常有需要判断的if--eles操作,层层嵌套,看起来程序的可读性太差,结合策略模式进行改造

    方法一、一般有策略模式  +  工厂模式进行代码的优化,减少 if---else;

    方法二、还有就是利用策略模式  +  SpringBoot提供的某些类  进行包装

    本次介绍采用方法二的方式,大概的思路是:

    1、策略模式:将所有同类型的操作抽象出来一个接口(这个接口包含一个动作方法) 和 一个实现了接口的抽象类(不实现方法);
    2、根据需求,将同类型的操作抽象成一个一个产品,继承第一步的抽象类,并实现抽象方法,编写自己的业务逻辑,注意此类需要注入spring容器;
    3、自定义一个类级别注解,用来区分不同操作类型的标识,此自定义标识要有返回一个类型的属性;
    4、抽象出来一个处理所有产品的公共HandlerContext对象,此对象提供一个获取具体产品类的方法,该方法有个入参用于表明是具体那个产品,同时该HandlerContext对象还具有Map类型的属性变量,
    存储key为具体的类型,value为具体的产品类对象,该Map对象通过构造函数的方式注入初始化进来;
    5、编写一个加载所有产品类的全局process类,用于扫描加了注解@HandlerType的所有实现产品,给存储key 和 value产品对象Map赋值,初始化HandlerContext 将其注册到spring容器中;

    需求

    这里虚拟一个业务需求,让大家容易理解。假设有一个订单系统,里面的一个功能是根据订单的不同类型作出不同的处理。

    订单实体:


    service接口:

        


    传统实现

       

    根据订单类型写一堆的if else:

    下面采用方法二来进行优化:

    1、策略模式:

    将所有同类型的操作抽象出来一个接口(这个接口包含一个动作方法) 和 一个实现了接口的抽象类(不实现方法);

    先定义一个数据传输的实体类DTO     OrderDTO

     1 @Data
     2 public class OrderDTO {
     3 
     4     private String code;
     5 
     6     private BigDecimal price;
     7 
     8     /**
     9      * 订单类型
    10      * 1:普通订单
    11      * 2:团购订单
    12      * 3:促销订单
    13      */
    14     private String orderType;
    15 }

    定义一个抽象类的接口:

    IHandlerService
     1 /**
     2  * <p>Title: com.aier.cloud.biz.simplify</p>
     3  * <p>Company:爱尔集团信息中心</p>
     4  * <p>Copyright:Copyright(c)</p>
     5  * User: duanm
     6  * Date: 2019/10/31 15:33
     7  * Description: No Description
     8  */
     9 public interface IHandlerService {
    10 
    11     String handler(OrderDTO orderDTO);
    12 }

    AbstractHandlerService:抽象类

    1 public abstract class AbstractHandlerService  implements IHandlerService {
    2 
    3     abstract public String handler(OrderDTO orderDTO);
    4 
    5 }

    2、根据需求,将同类型的操作抽象成一个一个产品,继承第一步的抽象类,并实现抽象方法,编写自己的业务逻辑,注意此类需要注入spring容器;

    团购订单处理类:

    1 @Component
    2 @HandlerType(value = "2")
    3 public class GroupHandler extends AbstractHandlerService {
    4 
    5     @Override
    6     public String handler(OrderDTO orderDTO) {
    7         return "处理团购订单";
    8     }
    9 }

    普通订单处理类:

    1 @Component
    2 @HandlerType(value = "1")
    3 public class NormalHandler extends AbstractHandlerService {
    4 
    5     @Override
    6     public String handler(OrderDTO orderDTO) {
    7         return "处理普通订单";
    8     }
    9 }

    促销订单处理类:

    1 @Component
    2 @HandlerType(value = "3")
    3 public class PromotionHandler extends AbstractHandlerService {
    4     @Override
    5     public String handler(OrderDTO orderDTO) {
    6         return "处理促销订单";
    7     }
    8 }

    注意事项:必须添加  @Component  注解,注入Spring容器      下面编写自定义的注解实现  HandlerType

    3、自定义一个类级别注解,用来区分不同操作类型的标识,此自定义标识要有返回一个类型的属性;

    1 @Target({ElementType.TYPE})
    2 @Retention(RetentionPolicy.RUNTIME)
    3 @Documented
    4 @Inherited
    5 public @interface HandlerType {
    6     String value();
    7 }

    4、 抽象出来一个公共HandlerContext对象

      抽象出来一个处理所有产品的公共HandlerContext对象,此对象提供一个获取具体产品类的方法,该方法有个入参用于表明是具体那个产品,同时该HandlerContext对象还具有Map类型的属性变量,
    存储key为具体的类型,value为具体的产品类对象,该Map类型变量初始化通过构造函数的方式注入;

     1 public class HandlerContext {
     2 
     3     private Map<String, Class> handlerMap;
     4 
     5     public HandlerContext(Map<String, Class> handlerMap) {
     6         this.handlerMap = handlerMap;
     7     }
     8 
     9     public AbstractHandlerService getInstance(String type) {
    10         Class clazz = handlerMap.get(type);
    11         if (clazz == null) {
    12             throw new IllegalArgumentException("not found handler for type : " + type);
    13         }
    14         return (AbstractHandlerService) BeanTool.getBean(clazz);
    15     }
    16 }

    5、编写一个加载所有产品类的全局process类,用于扫描加了注解@HandlerType的所有实现产品,给存储key 和 value产品对象Map赋值,初始化HandlerContext 将其注册到spring容器中;

     1 @Component
     2 public class HandlerProcessor implements BeanFactoryPostProcessor {
     3 
     4     private static final String HANDLER_PACKAGE = "com.aier.cloud.biz.simplify";
     5 
     6     /**
     7      * 扫描@HandlerType,初始化HandlerContext 将其注册到spring容器中
     8      *
     9      * @param configurableListableBeanFactory
    10      * @throws BeansException
    11      */
    12     @Override
    13     public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    14         Map<String, Class> handlerMap = Maps.newHashMap();
    15 
    16         ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
    17         provider.addIncludeFilter(new AnnotationTypeFilter(HandlerType.class));
    18         Set<BeanDefinition> candidateComponents = provider.findCandidateComponents(HANDLER_PACKAGE);
    19         candidateComponents.forEach(Beanclass -> {
    20             try {
    21                 Class<?> clazz = Class.forName(Beanclass.getBeanClassName());
    22                 //获取注解中的类型值
    23                 String type = clazz.getAnnotation(HandlerType.class).value();
    24                 //将注解中的类型值做为key,对应的类作为value 保存在handlerMap中
    25                 handlerMap.put(type, clazz);
    26             } catch (ClassNotFoundException e) {
    27                 e.printStackTrace();
    28             }
    29         });
    30 
    31         //初始化HandlerContext类,将其注入到Spring容器中
    32         HandlerContext handlerContext = new HandlerContext(handlerMap);
    33         configurableListableBeanFactory.registerSingleton(HandlerContext.class.getName(), handlerContext);
    34 
    35     }
    36 }

    注意:主要使用了spring的资料加载工具类,把所有的产品实现类都扫描 存储到map中,并利用继承  BeanFactoryPostProcessor   通过实现它的方法,动态的注入 HandlerContext 进入spring容器

    自定义注解和抽象处理器都很简单,那么如何将处理器注册到spring容器中呢?

    具体思路是:

    1、扫描指定包中标有@HandlerType的类;

    2、将注解中的类型值作为key,对应的类作为value,保存在Map中;

    3、以上面的map作为构造函数参数,初始化HandlerContext,将其注册到spring容器中;

    几个关键的工具类:

     1 @Component
     2 public class SpringContextUtils implements ApplicationContextAware {
     3 
     4     private static ApplicationContext applicationContext;
     5 
     6     @Override
     7     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
     8         SpringContextUtils.applicationContext = applicationContext;
     9     }
    10 
    11     /**
    12      * 取得存储在静态变量中的ApplicationContext.
    13      */
    14     public static ApplicationContext getApplicationContext() {
    15         checkApplicationContext();
    16         return applicationContext;
    17     }
    18 
    19     /**
    20      * 清除applicationContext静态变量.
    21      */
    22     public static void cleanApplicationContext() {
    23         applicationContext = null;
    24     }
    25 
    26     private static void checkApplicationContext() {
    27         if (applicationContext == null) {
    28             throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder");
    29         }
    30     }
    31 
    32     /**
    33      * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
    34      */
    35     @SuppressWarnings("unchecked")
    36     public static <T> T getBean(String name) {
    37         checkApplicationContext();
    38         return (T) applicationContext.getBean(name);
    39     }
    40 
    41     /**
    42      * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
    43      */
    44     @SuppressWarnings("unchecked")
    45     public static <T> T getBean(Class<T> clazz) {
    46         checkApplicationContext();
    47         return (T) applicationContext.getBeansOfType(clazz);
    48     }
    49 }
     1 public class BeanTool {
     2 
     3     public static  <T> T getBean(Class<T> clazz) {
     4         String clazzName = clazz.getName();
     5         clazzName = clazzName.replace(".",",");
     6         String[] split = clazzName.split(",");
     7         String name = split[split.length - 1];
     8         return SpringContextUtils.getBean(lowerFirst(name));
     9     }
    10 
    11     public static String lowerFirst(String oldStr) {
    12         char[] chars = oldStr.toCharArray();
    13         chars[0] += 32;
    14         return String.valueOf(chars);
    15     }
    16 }

    注意此处可以优化下,可以简单来获取类的名字,  

    1 public static  <T> T getBean(Class<T> clazz) {
    2         //获取类的名字
    3         String simpleName = clazz.getSimpleName();
    4         //根据类的名称获取类的实列对象
    5         return SpringContextUtils.getBean(lowerFirst(simpleName));
    6     }

    思考:  还是JAVA反射 不太熟悉,导致走了弯路来处理,JAVA反射还是要吃透,多写写。


    (1)反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。

    (2)通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
    (3)使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法。
    (4)反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。

    6、测试运行代码,查看结果

    测试运行:在service层进行调用,可以如下来编写:

     1 @Service
     2 public class OrderServiceImpl implements IOrderService {
     3 
     4     @Resource
     5     private HandlerContext handlerContext;
     6 
     7     @Override
     8     public String handle(OrderDTO orderDTO) {
     9         IHandlerService instance = handlerContext.getInstance(orderDTO.getOrderType());
    10         System.out.println(instance.handler(orderDTO));
    11         return instance.handler(orderDTO);
    12     }
    13 }

    运行结果如下:

    最后请注意一点,HandlerProcessor和BeanTool必须能被扫描到,或者通过@Bean的方式显式的注册,才能在项目启动时发挥作用。

    总结

    利用策略模式可以简化繁杂的if else代码,方便维护,而利用自定义注解和自注册的方式,可以方便应对需求的变更。本文只是提供一个大致的思路,还有很多细节可以灵活变化,例如使用枚举类型、或者静态常量,作为订单的类型,相信你能想到更多更好的方法。

    2、后续追加更优雅处理

    针对实现了策略模式的具体操作类,在业务处理类中,可以通过spring的已有功能进行处理,怎么通过不同的类型,获取到该类型的实现类。

    通过业务类的构造方法,

    hashMap  hp = new hashMap();// 最好采用线程安全的MAP对象,需要优化
    
    @Autowired
    public AemrMessageServiceImpl(List<策略模式的接口对象参数>){
       // 初始化map,存储起来
        
       map.put(业务类型key,业务类型实现)   
    }

    思路:

    1、通过spring的IOC快速实现通过类型type注入这个type的所有实现;

    2、通过申明一个线程安全的Map初始化数据,在业务类的构造函数中初始化进来,map.put(业务类型,策略模式业务的具体实现类);

    3、在具体使用的业务类处理方法中,通过类型获从map中拿到具体的实现类型,完成调用;

          

  • 相关阅读:
    qt用mingw编译时报错 multiple definition of
    作用域详解
    webpack-dev-server
    前端致命错误汇总
    那些我不知道的基础知识
    常见英语词汇第一记
    CSS权重;慎用!important
    校验正确获取对象或者数组的属性方法(babel-plugin-idx/_.get)
    同源策略
    用CSS实现梯形图标
  • 原文地址:https://www.cnblogs.com/yinfengjiujian/p/11773832.html
Copyright © 2011-2022 走看看