基於接口編程:使用收集器模式使數據獲取流程更加清晰可配置

背景

訂單導出中,經常須要從多個數據源獲取數據,並組裝數據詳情。常規寫法是這樣的:html

public List<T> produceOrderDetails(List<String> keys, Context context){

   // STEP1
   List<OrderInfo> orderInfoList = obtainFromDetail(keys, context);
   List<OrderInfo> oldOrderInfoList= obtainFromHBase(keys, context);
   orderInfoList = merge(orderInfoList, oldOrderInfoList);

   // STEP2
   List<OrderItemInfo> orderItemInfoList = obtainFromDetail(keys, context);
   List<OrderItemInfo> oldOrderItemInfoList= obtainFromHBase(keys, context);
   orderItemInfoList = merge(orderItemInfoList, oldOrderItemInfoList);

  // STEP3
  orderInfoList = obtainAllOrderLevelInfo(keys, context, orderInfoList);
 
  // STEP4
  orderItemInfoList = obtainAllOrderItemLevelInfo(keys, context, orderItemInfoList);

  // STEP5:
  // other codes ...
}

看上去是否是有些混亂?java

本文將應用「基於接口編程」的思想,改造這個流程,使之更清晰,可配置化。

spring

基於接口編程

基於接口編程,提倡不直接編寫具體實現,而是先定義接口及交互關係, 而後編寫接口的多個組件實現,最後,經過組件編排將實現串聯起來。

express

確立數據模型

首要的是確立數據模型。 數據詳情獲取的整個流程,都會圍繞這個數據模型而展開。編程

在這個例子中, 能夠看到, 主要的數據對象是 List<OrderInfo>, List<OrderItemInfo> , 分別對應訂單級別和商品級別的信息。在獲取數據的過程當中,將源源不斷的新數據詳情充填這兩個對象。緩存

@Data
public class OrderInfo {

  private String orderNo;

  public OrderInfo(String orderNo) {
    this.orderNo = orderNo;
  }
}
@Data
public class OrderItemInfo {

  private String orderNo;
  private String itemId;

  private String expressId;

  public OrderItemInfo(String orderNo, String itemId) {
    this.orderNo = orderNo;
    this.itemId = itemId;
  }
}


定義接口

基於數據模型,定義數據收集的接口。數據結構

public interface OrderDetailCollector {
  void collect(List<OrderInfo> orderInfoList, List<OrderItemInfo> orderItemInfoList);
}


組件實現

編寫組件類,使用適配器,將現有獲取數據的實現遷入。 組件化是配置化的前提。多線程

@Component("baseOrderDetailCollector")
public class BaseOrderDetailCollector implements OrderDetailCollector {

  @Override
  public void collect(List<OrderInfo> orderInfoList, List<OrderItemInfo> orderItemInfoList) {
    // 這裏可使用適配器
    orderInfoList.addAll(Arrays.asList(new OrderInfo("E001"), new OrderInfo("E002")));
    orderItemInfoList.addAll(Arrays.asList(new OrderItemInfo("E001", "I00001"), new OrderItemInfo("E002", "I000002")));
  }
}
@Component("expressOrderDetailCollector")
public class ExpressOrderDetailCollector implements OrderDetailCollector {

  @Override
  public void collect(List<OrderInfo> orderInfoList, List<OrderItemInfo> orderItemInfoList) {
    orderItemInfoList.forEach(
        orderItemInfo ->  orderItemInfo.setExpressId("EXP")
    );
  }
}


組件工廠

在實現一系列組件後,須要建立一個組件工廠,讓客戶端方便地獲取組件實現類。 一般會用到 ApplicationContextAware 這個接口。app

@Component("orderDetailCollectorFactory")
public class OrderDetailCollectorFactory implements ApplicationContextAware {

  private static Logger logger = LoggerFactory.getLogger(OrderDetailCollectorFactory.class);

  private ApplicationContext applicationContext;

  private Map<String, OrderDetailCollector> orderDetailCollectorMap;

  private static boolean hasInitialized = false;

  @PostConstruct
  public void init() {
    try {
      if(!hasInitialized){
        synchronized (OrderDetailCollectorFactory.class){
          if(!hasInitialized) {
            orderDetailCollectorMap = applicationContext.getBeansOfType(OrderDetailCollector.class);
            logger.info("detailCollectorMap: {}", orderDetailCollectorMap);
          }
        }
      }
    } catch (Exception ex) {
      logger.error("failed to load order detail collector !");
      throw new RuntimeException(ServerError.getMessage());
    }

  }

  public OrderDetailCollector get(String name) {
    return orderDetailCollectorMap.get(name);
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }
}


客戶端使用

一般,在具備一系列組件實現後,客戶端就能夠經過配置的方式來靈活選取和編排組件,實現靈活多變的功能。ide

public class CollectorClient {

  @Resource
  OrderDetailCollectorFactory orderDetailCollectorFactory;

  public void usage() {

    // 能夠配置在 DB 或 Apollo 裏
    List<String> collectors = Arrays.asList("baseOrderDetailCollector", "expressOrderDetailCollector");

    List<OrderInfo> orderInfos = new ArrayList<>();
    List<OrderItemInfo> orderItemInfos = new ArrayList<>();

    collectors.forEach(
        collector -> orderDetailCollectorFactory.get(collector).collect(orderInfos, orderItemInfos)
    );

  }
}


單例模式

上文中的 Collector 組件及工廠 OrderDetailCollectorFactory 都應用了單例模式。 如何實現單例模式呢 ?

DCL

注意下面的代碼使用了 DCL (雙重檢測鎖定)。

if(!hasInitialized){
        synchronized (OrderDetailCollectorFactory.class){
          if(!hasInitialized) {
            orderDetailCollectorMap = applicationContext.getBeansOfType(OrderDetailCollector.class);
            logger.info("detailCollectorMap: {}", orderDetailCollectorMap);
          }
        }
      }

不過這樣寫是不嚴謹的。

  • 若是是一個線程,只執行一次,那麼並不須要寫成這麼複雜,不須要使用 if (!hasInitialized) 和 synchronized 。
  • 若是是多線程,因爲 hasInitialized 並無被置爲 true ,所以,每一個進入的線程都會執行一遍這裏的邏輯。
  • 即便 hasInitialized 設置爲 true , 因爲不必定被其餘線程看到,所以,每一個進入的線程依然會執行一遍這裏的邏輯。

要更加嚴謹一些,應該寫成以下形式:

private volatile static boolean hasInitialized = false;

if(!hasInitialized){
        synchronized (OrderDetailCollectorFactory.class){
          if(!hasInitialized) {
            orderDetailCollectorMap = applicationContext.getBeansOfType(OrderDetailCollector.class);
            logger.info("detailCollectorMap: {}", orderDetailCollectorMap);
            hasInitialized = true;
          }
        }
      }

volatile 含「禁止指令重排序」的語義,保證 hasInitialized 修改後對其餘線程可見 。當一個線程修改了這個變量的值,volatile 保證了新值能當即同步到主內存,以及每次使用前當即從主內存刷新。


Spring單例

@Component 是 Spring bean 組件的註解,默認會建立成單例。 那麼 Spring 是如何實現單例模式的呢 ?

Spring 3.2.13 版本中,類 DefaultSingletonBeanRegistry 是 Spring 用來管理 singleton 實例的註冊表,實現單例的註冊、獲取和銷燬。 單例經過 getSingleton 方法獲取。要點以下:

  • 數據結構:使用 ConcurrentHashMap singletonObjects 來存儲單例名稱及實例的映射關係; 使用 Map earlySingletonObjects 做爲單例的緩存;使用 LinkedHashSet registeredSingletons 存儲已經註冊的單例名稱及註冊順序;使用 ConcurrentHashMap singletonsCurrentlyInCreation 存儲單例是否正在建立中;使用 singletonFactories 存儲 bean 名稱及 Singleton 工廠對象的映射關係;使用 inCreationCheckExclusions 存儲不須要建立時不須要檢查的 bean 名稱;

  • 單例獲取: 首先從 singletonObjects 獲取指定 bean 名稱的對象; 若是沒有獲取到對象,而且當前對象在建立中,那麼從 earlySingletonObjects 中獲取;若是 earlySingletonObjects 中也沒有,那麼從 singletonFactories 的 singleton 工廠中去製造和獲取單例對象。獲取到指定單例對象後,會進行單例的註冊工做,對以上數據結構進行添加和刪除。

單例的建立入口是類 AbstractBeanFactory 的 doGetBean 方法。要點以下:

  • 最底層的建立是經過反射調用構造器來實現的;這裏有一些權限的判斷和封裝;

  • 使用 CglibSubclassingInstantiationStrategy 策略來初始化 Bean 。

  • 在 bean 建立先後,有一些鉤子方法,好比 是否使用代理 ( AbstractAutoProxyCreator.createProxy 方法 ),建立前與建立後的檢測; 建立前和建立後的初始化等。


小結

從上面例子可見,確立數據模型,定義接口,實現組件,進行組件編排,是使得代碼設計與實現更加清晰靈活的經常使用模式。

在實現功能服務或業務流程時,不是想到哪寫到哪,而是首先進行一番設計和構思,想清楚數據、接口、組件、交互,而後再進行編程,每每實現出來會更加清晰、靈活、可配置化。

業務就是配置。


參考資料

相關文章
相關標籤/搜索