分享

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

 汉无为 2024-03-22 发布于湖北

前言

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 ,匹配实例的那部分代码使用 cglib 代理进行重写, 示例如下:
注解:

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

processor:

@Configuration
public class ETPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
        implements MergedBeanDefinitionPostProcessorBeanFactoryAware 
{

    ...

    private NodeProxy nodeProxy;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.nodeProxy = new NodeProxy((ConfigurableListableBeanFactory) beanFactory);
    }

    // 中间代码基本与 @Autowired 源码一致,故而省略
    ...

    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 MethodInterceptor {

    private ConfigurableListableBeanFactory beanFactory;

    public NodeProxy(ConfigurableListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public Object getProxy(Class<?> clz){
        Enhancer enhancer = new Enhancer();
        enhancer.setInterfaces(new Class[]{clz});
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        List<Object> targetObjects = new ArrayList<>(beanFactory.getBeansOfType(getProxyClass(o)).values());
        Object result = null;
        for (Object object : targetObjects) {
            result = method.invoke(object, objects);
        }
        return result;
    }

    private Class<?> getProxyClass(Object o) throws ClassNotFoundException {
        String clzName = o.getClass().getName();
        int index = clzName.indexOf('$');
        if (index > 0) {
            clzName = clzName.substring(0, index);
        }
        return Class.forName(clzName);
    }
}

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

图片

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

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 intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    List<Object> targetObjects = new ArrayList<>(beanFactory.getBeansOfType(getProxyClass(o)).values());
    Object result = null;
    for (Object object : targetObjects) {
        if (DslUtils.get(object.getClass().getName()) != null) {
            result = method.invoke(object, objects);
        }
    }
    return result;
}

修改测试:

@ExtensionNode
private Engine engine;

@Test
public void testA() {
    DslUtils.setDslA();
    engine.launch();
}

@Test
public void testB() {
    DslUtils.setDslB();
    engine.launch();
}

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

结语

我总是会在结语闲扯几句,再次开篇不知此番能维系多久,期望能如昨日所言,心愿长存;其次,期望能获得一些反馈,无论是否正向,这是持久的动力;最后,作为xx贵族~,孤独的灵魂总要寻个寄托,否则躁动的心如何安放,哈哈~ 各位有钱的碰个钱场,没零钱的捧个人场。。。🤪

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多