DSL 系列(1) - 扩展点的论述与实现
前言
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 {
@Override
public void launch() {
System.out.println("aengine launched");
}
}
实例 B:
@Service
public class BEngine_1 implements Engine {
@Override
public void launch() {
System.out.print("union 1 + ");
}
}
@Service
public class BEngine_2 implements Engine {
@Override
public void launch() {
System.out.print("union 2 +");
}
}
@Service
public class BEngine_3 implements Engine {
@Override
public void launch() {
System.out.print("union 3");
System.out.println("bengine launched");
}
}
测试:
public class DefaultTest {
@Autowired
private Engine engine;
@Test
public void testA() {
// set dsl a
engine.launch();
}
@Test
public void testB() {
// set dsl b
engine.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 InstantiationAwareBeanPostProcessorAdapter
implements 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;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
throw new IllegalArgumentException(
"ETPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
}
this.nodeProxy = new NodeProxy((ConfigurableListableBeanFactory) beanFactory);
}
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
@Override
public void resetBeanDefinition(String beanName) {
this.injectionMetadataCache.remove(beanName);
}
@Override
@Nullable
public 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);
}
@Override
public 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);
}
@Nullable
private 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);
}
@Override
protected 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);
}
@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) {
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();
}
再跑一次单元测试可完美实现预期效果(温馨提示:因时间关系伪代码写的很糙,此处有极大的设计和发挥空间,后续系列中逐步展开探讨)。
结语
我的公众号《有刻》,尽量会每天更新一篇,邀请关注一波~,我们共同成长!
DSL 系列(1) - 扩展点的论述与实现的更多相关文章
- 「译」JUnit 5 系列:扩展模型(Extension Model)
原文地址:http://blog.codefx.org/design/architecture/junit-5-extension-model/ 原文日期:11, Apr, 2016 译文首发:Lin ...
- MVC学习系列——ModelBinder扩展
在MVC系统中,我们接受数据,运用的是ModelBinder 的技术. MVC学习系列——ActionResult扩展在这个系列中,我们自定义了XmlResult的返回结果. 那么是不是意味着能POS ...
- XAML实例教程系列 - 标记扩展(Markup Extensions) 六
XAML实例教程系列 - 标记扩展(Markup Extensions) 分类: Windows 8 Silverlight2012-06-21 13:00 1139人阅读 评论(0) 收藏 举报 扩 ...
- DSL 系列(2) - 插件的论述与实现
前言 本文主要探讨基于 DSL(domain specific language) 之上的插件设计,他们是领域的附属,为领域提供额外的服务,但领域不依赖于他们. 1. 论述 领域应当尽可能地去专注他的 ...
- iOS开发系列--App扩展开发
概述 从iOS 8 开始Apple引入了扩展(Extension)用于增强系统应用服务和应用之间的交互.它的出现让自定义键盘.系统分享集成等这些依靠系统服务的开发变成了可能.WWDC 2016上众多更 ...
- k8s入门系列之扩展组件(一)DNS安装篇
DNS (domain name system),提供域名解析服务,解决了难于记忆的IP地址问题,以更人性可读可记忆可标识的方式映射对应IP地址. Cluster DNS扩展插件用于支持k8s集群系统 ...
- WCF扩展系列 - 行为扩展(Behaviors)
原文地址:http://www.cnblogs.com/Creator/archive/2011/05/21/2052687.html 这个系列的第一部分将会重点关注WCF行为(behaviors), ...
- 【转】WCF扩展系列 - 行为扩展(Behaviors)
原文:https://www.cnblogs.com/Creator/archive/2011/05/21/2052687.html 这个系列的第一部分将会重点关注WCF行为(behaviors),W ...
- MVC学习系列——Filter扩展
在MVC中,Filter也是可以扩展的.在此,本人对Filter的理解就是AOP,不知道各位大侠,有什么高的见解,呵呵... 首先MVC四大过滤神器IAuthorizationFilter,IActi ...
随机推荐
- Java:Hibernate报错记录:Error executing DDL via JDBC Statement
想着写一篇hibernate的博文,于是准备从头开始,从官网下了最新的稳定版本来做讲述. 结果利用hibernate自动建表的时候发生下面这个问题. 我很纳闷,之前用低版本一点的没有发生这个问题啊. ...
- element-ui的table动态生成表头和数据,且表中数据可编辑
1.实现表头的动态渲染 2.表头label和prop字段都要定义 3.去判断显示那个数据表 4.实现双击的时候在可编辑 // 双击修改 弹出input tableDbEdit(row, column, ...
- 最新的windows xp sp3序列号 xp序列号
最新的windows xp sp3序列号(绝对可通过正版验证) MRX3F-47B9T-2487J-KWKMF-RPWBY(工行版) 可用(强推此号) QC986-27D34-6M3TY-JJXP9- ...
- 洗礼灵魂,修炼python(76)--全栈项目实战篇(4)—— 购物车系统
要求: 1.基本符合日常购物车的要求(根据你的想法开放性提升功能) 2.展示商品信息,并且可随时上新商品 3.用户购买每一样商品时都对所剩的钱做一次对比,如果够则提示“已购买”,如果不够提示“余额不足 ...
- Java的基础知识二
一.方法函数 函数也称为方法,就是定义在类中的具有特定功能的一段独立代码.用于定义功能,提高代码的复用性. 函数的特点1> 定义函数可以将功能代码进行封装,便于对该功能进行复用:2> 函数 ...
- LeetCode算法题-Remove Linked List Elements(Java实现)
这是悦乐书的第189次更新,第191篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第48题(顺位题号是203).移除单链表中节点值为val的节点.例如: 输入:1-> ...
- C - 继续畅通工程 最小生成树
省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可).现得到城镇道路统计表,表中列出了任意两城镇间修建道路的费用,以及该道路是否已经 ...
- Loj #2731 「JOISC 2016 Day 1」棋盘游戏
Loj 2731 「JOISC 2016 Day 1」棋盘游戏 JOI 君有一个棋盘,棋盘上有 \(N\) 行 \(3\) 列 的格子.JOI 君有若干棋子,并想用它们来玩一个游戏.初始状态棋盘上至少 ...
- 【SDOI2014】向量集
[SDOI2014]向量集 题目描述 我们分析一波: 假设我们询问\((A,B)\),\(x_i>x_j\)若 \[ A\cdot x_i+B\cdot y_i>A\cdot x_j+B\ ...
- UVA1627-Team them up!(二分图判断+动态规划)
Problem UVA1627-Team them up! Total Submissions:1228 Solved:139 Time Limit: 3000 mSec Problem Descr ...