前言
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 MergedBeanDefinitionPostProcessor, BeanFactoryAware {
...
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贵族~,孤独的灵魂总要寻个寄托,否则躁动的心如何安放,哈哈~ 各位有钱的碰个钱场,没零钱的捧个人场。。。🤪
|