DSL 系列(2) - 插件的論述與實現

前言

本文主要探討基於 DSL(domain specific language) 之上的插件設計,他們是領域的附屬,爲領域提供額外的服務,但領域不依賴於他們。
java

1. 論述

領域應當儘量地去專一他的核心業務規則,應當儘量地與其餘輔助性的代碼解耦,一些通用的功能能夠耦合進框架或者設計爲中間件;但還存在有一些是與核心功能無關的,且又與業務邏輯密不可分,譬如特定的監控、特定的埋點、爲領域定製的穩定性保障等,把他們定義爲插件再合適不過,其依賴關係如前言所述。express

2. 設計方案

暫不討論特定的插件要實現哪些特定的能力,後續系列中將逐步展開構建一個完整的 DSL 具體須要哪些插件及其實現方案,這裏我想展開思考的是怎樣設計一個比較通用的 DSL 插件方案。緩存

論述中對插件的定義與 AOP 的思想至關吻合,也當首選使用 AOP 來實現,但這其中還存在一個問題,我但願插件只專一其自身職責的表達,至於哪些節點須要接入哪些插件應當在 DSL 中配置(即我指望插件與 DSL 之間只存在配置關係),而配置應當支持動態更新,所以這就致使了 AOP 的代理對象事先是不肯定的,須要去動態生成。app

最後落到實現上,插件這塊我須要去攻克兩個核心技術點:
一、怎樣去更新 AOP 的代理及切點表達式?
二、怎樣去更新 IOC 容器?框架

3. 實現方案

3.1 配置入口

若不考慮動態更新,那麼入口要實現的基本功能有兩個:一、按需引入,這很簡單,用一個 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();
    }
}

3.2 監聽 DLS 變動

怎麼監聽配置的更新是全部的配置中心都須要去深刻設計的(後續系列中探討),此處暫用僞代碼代替:post

@Configuration
public class PluginListenerImpl implements DslListener {

    @Override
    public void refresh(DslContext dslContext) {
        // do something...
    }
}

3.3 更新切點表達式

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);
    }
}

3.3 更新動態代理

經過翻閱源碼可得出 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);
    }

}

結果以下:

經過這種方式更新能夠不用擔憂屢次刷新代理對象產生的反作用,由於最終變化的只是代理類所匹配切面通知而已。

3.4 更新 Spring Context

開碼以前我一直認爲這一步是難點,刷了一遍源碼後發覺這一步異常簡單(看源碼仍是很重要...)。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 不一樣,說明更新成功。

3.5 更新 IOC 容器

這是最關鍵的一步,從操做數量上來看也是最重的一步,咱們來回顧下,到此咱們已經刷新了代理、刷新了切面通知、並將變動提交到了 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();
}

正如預期效果:

結語

靈明無著,物來順應,將來不迎,當下不雜,既過不戀~


個人公衆號《捷義》
qrcode_for_gh_c1a4cd5ae0fe_430.jpg

相關文章
相關標籤/搜索