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)反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
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中拿到具体的实现类型,完成调用;