DSL 全称为 domain-specific language(领域特定语言),本系列应当会很长,其中包含些许不成熟的想法,欢迎私信指正。java
我理解的 DSL 的主要职能是对领域的描述,他存在于领域服务之上,以下图所示:node
其实,咱们也能够认为 DomainService 是 AggregateRoot 的 DSL,区别是 DomainService 表达的是更原子化的描述,下图是我理解的更通俗的层次关系:dom
一句话总结:DSL 应当如同代码的组装说明书,他描述了各个子域的关系及其表达流程。ide
扩展点,顾名思义其核心在于扩展二字,若是你的领域只表达一种形态,那不必关注他。但假设你的领域存在不一样维度或者多种形式的表达,那扩展点极具价值,以下图所示:post
此时代码中的各个子域都成为了各类类型的标准件,而扩展点能够看作领域的骨架,由他限定整个域的职责(好比规定这个工厂只能生产汽车),而后由 DSL 去描述该职责有哪些表达(好比生产哪一种型号的车)。单元测试
在实现功能以前,我简单写了如下伪代码:
接口:测试
public interface Engine { void launch(); }
实例 A:ui
@Service public class AEngine implements Engine { @Override public void launch() { System.out.println("aengine launched"); } }
实例 B:this
@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"); } }
测试:spa
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
一对一的路由就是依赖注入,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; } }
此时咱们跑一下单元测试,获得:
一对多实例路由完美实现。
零件有了,骨架有了,最后就是怎样给他加一张图纸,让扩展点按需表达,伪代码以下:
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(); }
再跑一次单元测试可完美实现预期效果(舒适提示:因时间关系伪代码写的很糙,此处有极大的设计和发挥空间,后续系列中逐步展开探讨)。
个人公众号《捷义》