DSL 系列(1) - 扩展点的论述与实现 2019-04-28

前言

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

1. DSL 简述

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

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

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

2. 扩展点论述

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

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

3. 扩展点的实现方案

3.1 效果预期

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

public interface Engine { void launch();}

实例 A:

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

实例 B:

@Servicepublic class BEngine_1 implements Engine { @Override public void launch() { System.out.print("union 1 + "); }}@Servicepublic class BEngine_2 implements Engine { @Override public void launch() { System.out.print("union 2 +"); }}@Servicepublic 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:

@Configurationpublic 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); } } }}

代理:

@Configurationpublic 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); }}

修改代理:

@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) { 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;}

修改测试:

@ExtensionNodeprivate Engine engine;@Testpublic void testA() { DslUtils.setDslA(); engine.launch();}@Testpublic void testB() { DslUtils.setDslB(); engine.launch();}

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

结语

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

Copyright © 2019 126直营网平台登录 All Rights Reserved
苏晨
地址:江西省南昌市南昌路22号
全国统一热线:13616758064