本文主要探討基於 DSL(domain specific language) 之上的插件設計,他們是領域的附屬,爲領域提供額外的服務,但領域不依賴於他們。
java
領域應當儘量地去專一他的核心業務規則,應當儘量地與其餘輔助性的代碼解耦,一些通用的功能能夠耦合進框架或者設計爲中間件;但還存在有一些是與核心功能無關的,且又與業務邏輯密不可分,譬如特定的監控、特定的埋點、爲領域定製的穩定性保障等,把他們定義爲插件再合適不過,其依賴關係如前言所述。express
暫不討論特定的插件要實現哪些特定的能力,後續系列中將逐步展開構建一個完整的 DSL 具體須要哪些插件及其實現方案,這裏我想展開思考的是怎樣設計一個比較通用的 DSL 插件方案。緩存
論述中對插件的定義與 AOP 的思想至關吻合,也當首選使用 AOP 來實現,但這其中還存在一個問題,我但願插件只專一其自身職責的表達,至於哪些節點須要接入哪些插件應當在 DSL 中配置(即我指望插件與 DSL 之間只存在配置關係),而配置應當支持動態更新,所以這就致使了 AOP 的代理對象事先是不肯定的,須要去動態生成。app
最後落到實現上,插件這塊我須要去攻克兩個核心技術點:
一、怎樣去更新 AOP 的代理及切點表達式?
二、怎樣去更新 IOC 容器?框架
若不考慮動態更新,那麼入口要實現的基本功能有兩個:一、按需引入,這很簡單,用一個 Conditional
便可;二、加載一個表達式類型的通知器,示例以下:dom
@Configuration @ConditionalOnBean(DSL.class) public class PluginConfig { @Bean public AspectJExpressionPointcutAdvisor pluginAdvisor() { AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); advisor.setExpression(DSL.getExpression()); advisor.setAdvice(new PluginAdvice()); return advisor; } } public class PluginAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("do plugin_work start..."); Object resObj = invocation.proceed(); System.out.println("do plugin_work end..."); return resObj; } }
測試:ide
@RunWith(SpringRunner.class) @SpringBootTest public class DefaultTest { @ExtensionNode private Engine engine; @Test public void test() { DslUtils.setDslA(); engine.launch(); } }
怎麼監聽配置的更新是全部的配置中心都須要去深刻設計的(後續系列中探討),此處暫用僞代碼代替:post
@Configuration public class PluginListenerImpl implements DslListener { @Override public void refresh(DslContext dslContext) { // do something... } }
3.1 中咱們已經注入了一個表達式通知器的 Bean:AspectJExpressionPointcutAdvisor
,所以僅僅更新表達式的字符串很是簡單,但查看查看源碼會發現起匹配做用的是他的內部對象 AspectJExpressionPointcut
,而他在首次執行匹配時會構建一個 PointcutExpression
並保存起來:測試
private PointcutExpression obtainPointcutExpression() { if (getExpression() == null) { throw new IllegalStateException("Must set property 'expression' before attempting to match"); } if (this.pointcutExpression == null) { this.pointcutClassLoader = determinePointcutClassLoader(); this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader); } return this.pointcutExpression; }
所以咱們還須要經過反射將這個私有字段置空,讓 ClassFilter 從新執行構建,示例以下:ui
@Configuration public class PluginListenerImpl implements DslListener { @Autowired private AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor; @Override public void refresh(DslContext dslContext) throws NoSuchFieldException, IllegalAccessException { refreshExpression(dslContext); // next... } private void refreshExpression(DslContext dslContext) throws NoSuchFieldException, IllegalAccessException { aspectJExpressionPointcutAdvisor.setExpression(dslContext.getExpression()); AspectJExpressionPointcut pointcut = (AspectJExpressionPointcut) aspectJExpressionPointcutAdvisor.getPointcut().getClassFilter(); Field f = AspectJExpressionPointcut.class .getDeclaredField("pointcutExpression"); f.setAccessible(true); f.set(pointcut, null); } }
經過翻閱源碼可得出 Spring AOP 主要經過:AbstractAdvisingBeanPostProcessor 、AbstractAutoProxyCreator
這兩個 processor 來實現動態代理,其對應的實例爲:MethodValidationPostProcessor、AnnotationAwareAspectJAutoProxyCreator
,前者用於建立代理對象,後者用於標記切面(即織入代理)。由此,若咱們須要去更新動態代理,我想到的最簡單的方法就是對指定的節點從新執行如下這兩個 processor(原理簡單,就是一點點扣源碼,麻煩...),其中還有一個小問題,和 3.2 中的一致,代理結果被緩存了,清空再執行便可,示例以下:
@Autowired private DefaultListableBeanFactory defaultListableBeanFactory; private void refreshTypes(DslContext dslContext) throws NoSuchFieldException, IllegalAccessException { List<Class<?>> refreshTypes = dslContext.getRefreshTypes(); for (Class<?> refreshType : refreshTypes) { String[] beanNames = defaultListableBeanFactory.getBeanNamesForType(refreshType); for (String beanName : beanNames) { Object bean = defaultListableBeanFactory.getBean(beanName); for (BeanPostProcessor processor : defaultListableBeanFactory.getBeanPostProcessors()) { bean = getProxyBean(bean, beanName, processor); } } } } private Object getProxyBean(Object bean, String beanName, BeanPostProcessor processor) throws NoSuchFieldException, IllegalAccessException { if (processor instanceof MethodValidationPostProcessor || processor instanceof AnnotationAwareAspectJAutoProxyCreator) { removeAdvisedBeanCache(processor, bean, beanName); Object current = processor.postProcessAfterInitialization(bean, beanName); return current == null ? bean : current; } return bean; } private void removeAdvisedBeanCache(BeanPostProcessor processor, Object bean, String beanName) throws NoSuchFieldException, IllegalAccessException { if (processor instanceof AnnotationAwareAspectJAutoProxyCreator) { AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator = (AnnotationAwareAspectJAutoProxyCreator) processor; Field f = AnnotationAwareAspectJAutoProxyCreator.class .getSuperclass() .getSuperclass() .getSuperclass() .getDeclaredField("advisedBeans"); f.setAccessible(true); Map<Object, Boolean> advisedBeans = (Map<Object, Boolean>) f.get(annotationAwareAspectJAutoProxyCreator); Object cacheKey = getCacheKey(bean.getClass(), beanName); advisedBeans.remove(cacheKey); } } private Object getCacheKey(Class<?> beanClass, @Nullable String beanName) { if (StringUtils.hasLength(beanName)) { return (FactoryBean.class.isAssignableFrom(beanClass) ? BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName); } else { return beanClass; } }
到此能夠測試如下新生成的代理類:
public class PluginTest { @Autowired private BEngine bEngine; @Autowired private DslListener dslListener; @Test public void test() throws NoSuchFieldException, IllegalAccessException { System.out.println("--------proxy before-----------"); System.out.println("BEngine.class:" + bEngine.getClass()); bEngine.launch(); DslContext dslContext = new DslContext(); // 初始值爲 execution( void com.youclk.et.car.a.AEngine.launch() ),BEngine 並未被代理 dslContext.setExpression("execution( void com.youclk.et.car.b.BEngine.launch() )"); dslContext.setRefreshTypes(Collections.singletonList(BEngine.class)); dslListener.refresh(dslContext); } }
結果以下:
經過這種方式更新能夠不用擔憂屢次刷新代理對象產生的反作用,由於最終變化的只是代理類所匹配切面通知而已。
開碼以前我一直認爲這一步是難點,刷了一遍源碼後發覺這一步異常簡單(看源碼仍是很重要...)。DefaultListableBeanFactory
其實有提供 remove 和 register 方法用於更新 Bean,可是這兩步的操做我認爲過重了,並且在 remove 和 register 之間用到了這個 Bean 怎麼辦,所以存在極大風險。且看咱們上一步作了什麼,從 BeanDefinition 這個維度看咱們只更新了 classType,其餘的都沒變,所以我考慮只要更新下 BeanDefinition,並清除對應的緩存便可,示例以下:
private void refreshTypes(DslContext dslContext) throws NoSuchFieldException, IllegalAccessException { List<Class<?>> refreshTypes = dslContext.getRefreshTypes(); for (Class<?> refreshType : refreshTypes) { String[] beanNames = defaultListableBeanFactory.getBeanNamesForType(refreshType); for (String beanName : beanNames) { Object bean = defaultListableBeanFactory.getBean(beanName); for (BeanPostProcessor processor : defaultListableBeanFactory.getBeanPostProcessors()) { bean = getProxyBean(bean, beanName, processor); } refreshBeanDefinition(beanName, bean.getClass()); } } } private void refreshBeanDefinition(String beanName, Class<?> classType) throws NoSuchFieldException, IllegalAccessException { RootBeanDefinition rootBeanDefinition = (RootBeanDefinition) defaultListableBeanFactory.getMergedBeanDefinition(beanName); rootBeanDefinition.setBeanClass(classType); ScannedGenericBeanDefinition scannedGenericBeanDefinition = (ScannedGenericBeanDefinition) defaultListableBeanFactory.getBeanDefinition(beanName); scannedGenericBeanDefinition.setBeanClass(classType); removeBeanDefinitionCache(beanName); } private void removeBeanDefinitionCache(String beanName) throws NoSuchFieldException, IllegalAccessException { Field factoryBeanObjectCache_f = DefaultListableBeanFactory.class .getSuperclass() .getSuperclass() .getSuperclass() .getDeclaredField("factoryBeanObjectCache"); factoryBeanObjectCache_f.setAccessible(true); Map<String, Object> factoryBeanObjectCache = (Map<String, Object>) factoryBeanObjectCache_f.get(defaultListableBeanFactory); factoryBeanObjectCache.remove(beanName); Field singletonObjects_f = DefaultListableBeanFactory.class .getSuperclass() .getSuperclass() .getSuperclass() .getSuperclass() .getDeclaredField("singletonObjects"); singletonObjects_f.setAccessible(true); Map<String, Object> singletonObjects = (Map<String, Object>) singletonObjects_f.get(defaultListableBeanFactory); singletonObjects.remove(beanName); Field singletonFactories_f = DefaultListableBeanFactory.class .getSuperclass() .getSuperclass() .getSuperclass() .getSuperclass() .getDeclaredField("singletonFactories"); singletonFactories_f.setAccessible(true); Map<String, Object> singletonFactories = (Map<String, Object>) singletonFactories_f.get(defaultListableBeanFactory); singletonFactories.remove(beanName); Field earlySingletonObjects_f = DefaultListableBeanFactory.class .getSuperclass() .getSuperclass() .getSuperclass() .getSuperclass() .getDeclaredField("earlySingletonObjects"); earlySingletonObjects_f.setAccessible(true); Map<String, Object> earlySingletonObjects = (Map<String, Object>) earlySingletonObjects_f.get(defaultListableBeanFactory); earlySingletonObjects.remove(beanName); }
測試下是否完成了個人預期:
@Autowired private ApplicationContext applicationContext; @Test public void testRefreshBeanDefinition() throws NoSuchFieldException, IllegalAccessException { System.out.println("--------refresh before-----------"); System.out.println("BEngine.class:" + applicationContext.getBean(bEngine.getClass()).getClass()); refresh(); System.out.println("--------refresh after-----------"); System.out.println("BEngine.class:" + applicationContext.getBean(bEngine.getClass()).getClass()); } private void refresh() throws NoSuchFieldException, IllegalAccessException { DslContext dslContext = new DslContext(); //初始值爲 execution( void com.youclk.et.car.a.AEngine.launch() ),BEngine 並未被代理 dslContext.setExpression("execution( void com.youclk.et.car.b.BEngine.launch() )"); dslContext.setRefreshTypes(Collections.singletonList(BEngine.class)); dslListener.refresh(dslContext); }
結果以下:
兩次獲取到的 classType 不一樣,說明更新成功。
這是最關鍵的一步,從操做數量上來看也是最重的一步,咱們來回顧下,到此咱們已經刷新了代理、刷新了切面通知、並將變動提交到了 Spring Context 中,咱們還缺最後一步:更新目標對象全部的依賴注入。
由於咱們須要將修改後的 Bean 從新注入全部依賴他的 Bean 中,這其中可能涉及到衆多的修改操做,所以第一步咱們要獲取全部的依賴注入關係,他們維護在:AutowiredAnnotationBeanPostProcessor.injectionMetadataCache
中;因爲一次提交可能涉及到多個目標對象的更新,他們之間又有存在依賴的可能性,所以第二步先把那一堆新的 bean 刷到 metadataCache,最後篩選出全部與更新相關的依賴,從新注入一遍,示例以下:
private AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor; private void refreshTypes(DslContext dslContext) throws Exception { List<Class<?>> refreshTypes = dslContext.getRefreshTypes(); HashMap<String, String> refreshBeans = new HashMap<>(); for (Class<?> refreshType : refreshTypes) { String[] beanNames = defaultListableBeanFactory.getBeanNamesForType(refreshType); for (String beanName : beanNames) { Object bean = defaultListableBeanFactory.getBean(beanName); for (BeanPostProcessor processor : defaultListableBeanFactory.getBeanPostProcessors()) { if (processor instanceof AutowiredAnnotationBeanPostProcessor) { autowiredAnnotationBeanPostProcessor = (AutowiredAnnotationBeanPostProcessor) processor; continue; } bean = getProxyBean(bean, beanName, processor); } refreshBeanDefinition(beanName, bean.getClass()); refreshBeans.put(beanName, getRealName(bean.getClass().getName())); } } refreshIoc(refreshBeans); } private void refreshIoc(HashMap<String, String> refreshBeans) throws Exception { for (String refreshBeanName : refreshBeans.keySet()) { resetInjectionMetadataCache(refreshBeanName); } Set<Object> beans = getReInjectionBeans(refreshBeans); for (Object bean : beans) { defaultListableBeanFactory.autowireBeanProperties(bean, 0, false); } } private void resetInjectionMetadataCache(String refreshBeanName) { autowiredAnnotationBeanPostProcessor.resetBeanDefinition(refreshBeanName); autowiredAnnotationBeanPostProcessor.determineCandidateConstructors(refreshBeanName.getClass(), refreshBeanName); RootBeanDefinition rootBeanDefinition = (RootBeanDefinition) defaultListableBeanFactory.getMergedBeanDefinition(refreshBeanName); Object bean = defaultListableBeanFactory.getBean(refreshBeanName); autowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(rootBeanDefinition, bean.getClass(), refreshBeanName); } private Set<Object> getReInjectionBeans(HashMap<String, String> refreshBeans) throws Exception { Field injectionMetadataCache_f = AutowiredAnnotationBeanPostProcessor.class.getDeclaredField("injectionMetadataCache"); injectionMetadataCache_f.setAccessible(true); Map<String, InjectionMetadata> factoryBeanObjectCache = (Map<String, InjectionMetadata>) injectionMetadataCache_f.get(autowiredAnnotationBeanPostProcessor); Set<Object> injectedBeanNames = new HashSet<>(); for (String beanName : factoryBeanObjectCache.keySet()) { Collection<InjectionMetadata.InjectedElement> injectedElements = getInjectedElements(factoryBeanObjectCache.get(beanName)); if (injectedElements == null) { continue; } for (InjectionMetadata.InjectedElement injectedElement : injectedElements) { if (refreshBeans.values().contains(getRealName(getResourceType(injectedElement).getName()))) { injectedBeanNames.add(defaultListableBeanFactory.getBean(beanName)); } } } return injectedBeanNames; } private Collection<InjectionMetadata.InjectedElement> getInjectedElements(InjectionMetadata injectionMetadata) throws Exception { Field injectedElements_f = InjectionMetadata.class.getDeclaredField("injectedElements"); injectedElements_f.setAccessible(true); Collection<InjectionMetadata.InjectedElement> injectedElements = (Collection<InjectionMetadata.InjectedElement>) injectedElements_f.get(injectionMetadata); return injectedElements; } private Class<?> getResourceType(InjectionMetadata.InjectedElement injectedElement) throws Exception { Method getResourceType_m = InjectionMetadata.InjectedElement.class.getDeclaredMethod("getResourceType"); getResourceType_m.setAccessible(true); return (Class<?>) getResourceType_m.invoke(injectedElement); } private String getRealName(String instanceName) { int index = instanceName.indexOf("$"); if (index > 0) { instanceName = instanceName.substring(0, index); } return instanceName; }
最後再來測試一波:
@Test public void test() throws Exception { bEngine.launch(); refresh(); bEngine.launch(); }
正如預期效果:
靈明無著,物來順應,將來不迎,當下不雜,既過不戀~
個人公衆號《捷義》