订单导出中,经常须要从多个数据源获取数据,并组装数据详情。常规写法是这样的: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 (双重检测锁定)。
if(!hasInitialized){ synchronized (OrderDetailCollectorFactory.class){ if(!hasInitialized) { orderDetailCollectorMap = applicationContext.getBeansOfType(OrderDetailCollector.class); logger.info("detailCollectorMap: {}", orderDetailCollectorMap); } } }
不过这样写是不严谨的。
要更加严谨一些,应该写成以下形式:
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 保证了新值能当即同步到主内存,以及每次使用前当即从主内存刷新。
@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 方法 ),建立前与建立后的检测; 建立前和建立后的初始化等。
从上面例子可见,确立数据模型,定义接口,实现组件,进行组件编排,是使得代码设计与实现更加清晰灵活的经常使用模式。
在实现功能服务或业务流程时,不是想到哪写到哪,而是首先进行一番设计和构思,想清楚数据、接口、组件、交互,而后再进行编程,每每实现出来会更加清晰、灵活、可配置化。
业务就是配置。