前言

DSL 全称为 domain-specific language(领域特定语言),本系列应当会很长,其中包含些许不成熟的想法,欢迎私信指正。

1. DSL 简述

我理解的 DSL 的主要职能是对领域的描述,他存在于领域服务之上,如下图所示:

其实,我们也可以认为 DomainService 是 AggregateRoot 的 DSL,区别是 DomainService 表达的是更原子化的描述,下图是我理解的更通俗的层次关系:

一句话总结:DSL 应当如同代码的组装说明书,他描述了各个子域的关系及其表达流程。

2. 扩展点论述

扩展点,顾名思义其核心在于扩展二字,如果你的领域只表达一种形态,那没必要关注他。但假设你的领域存在不同维度或者多种形式的表达,那扩展点极具价值,如下图所示:

此时代码中的各个子域都成为了各种类型的标准件,而扩展点可以看做领域的骨架,由他限定整个域的职责(比如规定这个工厂只能生产汽车),然后由 DSL 去描述该职责有哪些表达(比如生产哪种型号的车)。

3. 扩展点的实现方案

3.1 效果预期

在实现功能之前,我简单写了以下伪代码:
接口:

public interface Engine {void launch();
}

实例 A:

@Service
public class AEngine implements Engine {@Overridepublic void launch() {System.out.println("aengine launched");}
}

实例 B:

@Service
public class BEngine_1 implements Engine {@Overridepublic void launch() {System.out.print("union 1 + ");}
}@Service
public class BEngine_2 implements Engine {@Overridepublic void launch() {System.out.print("union 2 +");}
}@Service
public class BEngine_3 implements Engine {@Overridepublic void launch() {System.out.print("union 3");System.out.println("bengine launched");}
}

测试:

public class DefaultTest {@Autowiredprivate Engine engine;@Testpublic void testA() {// set dsl aengine.launch();}@Testpublic void testB() {// set dsl bengine.launch();}}

我期待的结果是当 testA 执行时输出:aengine launched,当 testB 执行时输出:union 1 + union 2 + union 3 bengine launched

3.2 实现接口到实例的一对多路由

一对一的路由就是依赖注入,Spring 已经帮我们实现了,那怎样实现一对多?我的想法是仿照 @Autowired ,匹配实例的那部分代码使用 jdk 代理进行重写, 示例如下:
注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExtensionNode {
}

Processor:

@Configuration
public class ETPostProcessor extends InstantiationAwareBeanPostProcessorAdapterimplements MergedBeanDefinitionPostProcessor, BeanFactoryAware {private final Log logger = LogFactory.getLog(getClass());private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache = new ConcurrentHashMap<>(256);private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);private NodeProxy nodeProxy;@Overridepublic void setBeanFactory(BeanFactory beanFactory) {if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {throw new IllegalArgumentException("ETPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);}this.nodeProxy = new NodeProxy((ConfigurableListableBeanFactory) beanFactory);}@Overridepublic void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);metadata.checkConfigMembers(beanDefinition);}@Overridepublic void resetBeanDefinition(String beanName) {this.injectionMetadataCache.remove(beanName);}@Override@Nullablepublic Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)throws BeanCreationException {// Quick check on the concurrent map first, with minimal locking.Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);if (candidateConstructors == null) {// Fully synchronized resolution now...synchronized (this.candidateConstructorsCache) {candidateConstructors = this.candidateConstructorsCache.get(beanClass);if (candidateConstructors == null) {Constructor<?>[] rawCandidates;try {rawCandidates = beanClass.getDeclaredConstructors();} catch (Throwable ex) {throw new BeanCreationException(beanName,"Resolution of declared constructors on bean Class [" + beanClass.getName() +"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);}List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);Constructor<?> requiredConstructor = null;Constructor<?> defaultConstructor = null;Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);int nonSyntheticConstructors = 0;for (Constructor<?> candidate : rawCandidates) {if (!candidate.isSynthetic()) {nonSyntheticConstructors++;} else if (primaryConstructor != null) {continue;}AnnotationAttributes ann = findETAnnotation(candidate);if (ann == null) {Class<?> userClass = ClassUtils.getUserClass(beanClass);if (userClass != beanClass) {try {Constructor<?> superCtor =userClass.getDeclaredConstructor(candidate.getParameterTypes());ann = findETAnnotation(superCtor);} catch (NoSuchMethodException ignore) {}}}if (ann != null) {if (requiredConstructor != null) {throw new BeanCreationException(beanName,"Invalid autowire-marked constructor: " + candidate +". Found constructor with 'required' ET annotation already: " +requiredConstructor);}requiredConstructor = candidate;candidates.add(candidate);} else if (candidate.getParameterCount() == 0) {defaultConstructor = candidate;}}if (!candidates.isEmpty()) {// Add default constructor to list of optional constructors, as fallback.candidateConstructors = candidates.toArray(new Constructor<?>[0]);} else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {candidateConstructors = new Constructor<?>[]{rawCandidates[0]};} else if (nonSyntheticConstructors == 2 && primaryConstructor != null &&defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) {candidateConstructors = new Constructor<?>[]{primaryConstructor, defaultConstructor};} else if (nonSyntheticConstructors == 1 && primaryConstructor != null) {candidateConstructors = new Constructor<?>[]{primaryConstructor};} else {candidateConstructors = new Constructor<?>[0];}this.candidateConstructorsCache.put(beanClass, candidateConstructors);}}}return (candidateConstructors.length > 0 ? candidateConstructors : null);}@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {metadata.inject(bean, beanName, pvs);} catch (BeanCreationException ex) {throw ex;} catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of ET dependencies failed", ex);}return pvs;}private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {// Fall back to class name as cache key, for backwards compatibility with custom callers.String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());// Quick check on the concurrent map first, with minimal locking.InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {synchronized (this.injectionMetadataCache) {metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {if (metadata != null) {metadata.clear(pvs);}metadata = buildAutowiringMetadata(clazz);this.injectionMetadataCache.put(cacheKey, metadata);}}}return metadata;}private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();Class<?> targetClass = clazz;do {final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();ReflectionUtils.doWithLocalFields(targetClass, field -> {AnnotationAttributes ann = findETAnnotation(field);if (ann != null) {if (Modifier.isStatic(field.getModifiers())) {if (logger.isInfoEnabled()) {logger.info("ET annotation is not supported on static fields: " + field);}return;}currElements.add(new ETPostProcessor.ETFieldElement(field));}});elements.addAll(0, currElements);targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);return new InjectionMetadata(clazz, elements);}@Nullableprivate AnnotationAttributes findETAnnotation(AccessibleObject ao) {if (ao.getAnnotations().length > 0) {AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ao, ExtensionNode.class);if (attributes != null) {return attributes;}}return null;}private class ETFieldElement extends InjectionMetadata.InjectedElement {ETFieldElement(Field field) {super(field, null);}@Overrideprotected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value = nodeProxy.getProxy(field.getType());if (value != null) {ReflectionUtils.makeAccessible(field);field.set(bean, value);}}}
}

代理:

@Configuration
public class NodeProxy implements InvocationHandler {private final ConfigurableListableBeanFactory beanFactory;public NodeProxy(ConfigurableListableBeanFactory beanFactory) {this.beanFactory = beanFactory;}public Object getProxy(Class<?> clazz) {ClassLoader classLoader = ClassUtils.getDefaultClassLoader();return Proxy.newProxyInstance(classLoader, new Class[]{clazz}, this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {List<Object> targetObjects = new ArrayList<>(beanFactory.getBeansOfType(method.getDeclaringClass()).values());Object result = null;for (Object object : targetObjects) {result = method.invoke(object, args);}return result;}
}

此时我们跑一下单元测试,得到:

一对多实例路由完美实现。

3.3 添加 DSL 描述

零件有了,骨架有了,最后就是怎样给他加一张图纸,让扩展点按需表达,伪代码如下:

public class DslUtils {private static final ThreadLocal<Map<String, Class<?>>> LOCAL = new ThreadLocal<>();public static void setDslA() {Map<String, Class<?>> map = new HashMap<>();map.put(AEngine.class.getName(), AEngine.class);LOCAL.set(map);}public static void setDslB() {Map<String, Class<?>> map = new HashMap<>();map.put(BEngine_1.class.getName(), BEngine_1.class);map.put(BEngine_2.class.getName(), BEngine_2.class);map.put(BEngine_3.class.getName(), BEngine_3.class);LOCAL.set(map);}public static Class<?> get(String name) {Map<String, Class<?>> map = LOCAL.get();return map.get(name);}
}

修改代理:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {List<Object> targetObjects = new ArrayList<>(beanFactory.getBeansOfType(method.getDeclaringClass()).values());Object result = null;for (Object object : targetObjects) {if (DslUtils.get(getRealName(object)) != null) {result = method.invoke(object, args);}}return result;
}private String getRealName(Object o) {String instanceName = o.getClass().getName();int index = instanceName.indexOf("$");if (index > 0) {instanceName = instanceName.substring(0, index);}return instanceName;
}

修改测试:

@ExtensionNode
private Engine engine;@Test
public void testA() {DslUtils.setDslA();engine.launch();
}@Test
public void testB() {DslUtils.setDslB();engine.launch();
}

再跑一次单元测试可完美实现预期效果(温馨提示:因时间关系伪代码写的很糙,此处有极大的设计和发挥空间,后续系列中逐步展开探讨)。

结语

我的公众号《有刻》,尽量会每天更新一篇,邀请关注一波~,我们共同成长!

转载于:https://www.cnblogs.com/youclk/p/10086684.html

DSL 系列(1) - 扩展点的论述与实现相关推荐

  1. Proteus8.9 VSM Studio WINAVR编译器仿真ATmega16系列a09_扩展内存

    *本文及代码参阅彭伟<单片机C语言程序设计实训100例> 一,打开文件(可以随文下载放置在文档中打开).(如下图1所示) 图1 二,调整原理图大小,适合可视,另存工程文件.(如下图2,3, ...

  2. ## ***电池SOC仿真系列-基于扩展卡尔曼(EKF)算法的SOC估计(内含代码等资料)***

    ## ***电池SOC仿真系列-基于扩展卡尔曼(EKF)算法的SOC估计(内含代码等资料)*** ## 1 研究背景 电池的荷电状态(SOC)代表的是电池当前的剩余容量,数值定义是电池剩余电量与电池额 ...

  3. Lync Server 2010迁移至Lync Server 2013部署系列 Part1: 扩展AD架构

    由于最近直在忙Lync 升级,好久没有更新博客了,今天开始将对最近做的Lync Server 2010迁移至Lync Server 2013项目做一个系列的部署操作更新,希望能给即将在企业中部署的兄弟 ...

  4. antlr4 代码 语法树_使用ANTLR4,用于代码镜像和基于Web的DSL的Primefaces扩展

    antlr4 代码 语法树 DSL是很酷的东西,但我不清楚它们有什么用. 然后我意识到它们对以下方面有好处: 摆脱复杂的UI 意思是 更快的做事方式 而已. 当我阅读此博客时,我得出了这个结论. 如果 ...

  5. 使用ANTLR4,用于代码镜像和基于Web的DSL的Primefaces扩展

    DSL是很酷的东西,但是我不清楚它们有什么用. 然后我意识到它们对以下方面有好处: 摆脱复杂的UI 意思是 更快的做事方式 而已. 当我阅读此博客时,我得出了这个结论. 如果您的用户是技术人员,并且不 ...

  6. ES6系列--对象扩展

    ES6允许直接写入变量和函数,作为对象的属性和方法. var foo = 'bar'; var baz = {foo}; baz // {foo: "bar"} var fa = ...

  7. MVVM架构~knockoutjs系列之扩展ajax验证~验证输入数据是否与后台数据相等

    返回目录 在看这篇文章之前,你有必要先看我之前的文章,之前文章是将一个方法以参数的形式传给KO,然后返回一个真假值,去做验证,这类似于面向对象语言里的委托,在JS里我们叫它回调方法,本篇文章与前一文章 ...

  8. ENVI系列--安装扩展工具的两种方法

    ENVI扩展工具 ENVI扩展工具现目前普遍适用于5.3版本以及5.3以上的版本.而且目前5.3以上的版本几乎都是实行申请使用的方式,一年能免费申请两次,一次貌似是45天的免费使用时间. ENVI 发 ...

  9. 【Vue3.0实战逐步深入系列】扩展投票功能基于elementui进行组件封装实现一个简单的问卷调查功能

    [千字长文,熬夜更新,原创不易,多多支持,感谢大家] 前言 小伙伴们大家好.在前面一偏文章中我们把投票功能进行了简单的改造:引入了axios第三方库并进行了二次封装用于模拟请求服务器数据.同时添加了一 ...

最新文章

  1. 构建高性能ASP.NET应用的12点建议
  2. css你所不知道技巧
  3. 天梯 L2 这是二叉搜索树吗?
  4. css实现文字过长省略显示
  5. js中select下拉框重置_如何利用CSS3制作炫酷的下拉框
  6. sql还原数据库备份数据库_有关数据库备份,还原和恢复SQL面试问题–第一部分
  7. 来看看优酷是如何测试 App 响应式布局的!
  8. AS3 XML全部用法
  9. 【Python】【jupyter-notebook】
  10. [android开发IDE]adt-bundle-windows-x86的一个bug:无法解析.rs文件--------rs_core.rsh file not found...
  11. attention机制的几种方法
  12. Python中文手册——开胃菜
  13. 分享一些学习资料-大量PDF电子书
  14. photoshop实用技巧
  15. html中搜索框提示语,请输入您要搜索的内容(自定义Win10搜索框提示语的技巧)...
  16. 用场景应用™玩转#冰桶挑战#
  17. C语言结构体中的冒号用法
  18. 一起教育科技2020净收1.92亿元 官网却启用杂米域名17zyw.cn
  19. maven本地仓库设置
  20. android手机车载投屏,手机车载投屏的方法安卓、苹果的都有

热门文章

  1. springboot做网站_SpringBoot项目实战(3):整合Freemark模板
  2. e5 e3 php,硬件百科:E3/E5为什么让“垃圾佬”痴迷
  3. 自回归AR模型、移动平均MA模型、自回归移动平均ARMA模型
  4. 程序员怒斥:虎牙HR真奇葩,通知我面试,又何必当面羞辱我一番?
  5. 一个好的web前端开发者,是怎么学习的?
  6. 前端培训什么机构好?有什么好的学习方法能少走弯路?
  7. 动态网页和静态网页的区别是什么?
  8. 郑州轻工业大学c语言考试题库,2016年郑州轻工业学院计算机与通信工程学院C语言程序设计考研复试题库...
  9. 计算机c语言笔试试题,计算机二级c语言笔试题和面试题答案(2019最新)
  10. 删除PHP配置文件中的注释行