zoukankan      html  css  js  c++  java
  • 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();
    }
    

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

    结语

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

    作者:捷义
    出处:http://www.cnblogs.com/youclk/
    说明:转载请标明来源和作者
  • 相关阅读:
    eureka的fetch-registry属性解释
    数据结构设计
    typescript let和const区别
    JDK8新特性
    Synchronized的内存可见性
    java实现打印功能
    idea单元测试jpa注入失败问题----来自Spring Cloud微服务实战-idea版的 廖师兄的product
    eclipse快速生成接口
    读取 Excel 之 Epplus
    [转][Dapper]参数化查询慢
  • 原文地址:https://www.cnblogs.com/youclk/p/10086684.html
Copyright © 2011-2022 走看看