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中拿到具體的實現類型,完成調用;

      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM