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"); } }
測試:設計
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(); }
再跑一次單元測試可完美實現預期效果(舒適提示:因時間關係僞代碼寫的很糙,此處有極大的設計和發揮空間,後續系列中逐步展開探討)。
個人公衆號《有刻》,儘可能會天天更新一篇,邀請關注一波~,咱們共同成長!