zoukankan      html  css  js  c++  java
  • DSL 系列(2)

    前言

    本文主要探讨基于 DSL(domain specific language) 之上的插件设计,他们是领域的附属,为领域提供额外的服务,但领域不依赖于他们。

    1. 论述

    领域应当尽可能地去专注他的核心业务规则,应当尽可能地与其他辅助性的代码解耦,一些通用的功能可以耦合进框架或者设计为中间件;但还存在有一些是与核心功能无关的,且又与业务逻辑密不可分,譬如特定的监控、特定的埋点、为领域定制的稳定性保障等,把他们定义为插件再合适不过,其依赖关系如前言所述。

    2. 设计方案

    暂不讨论特定的插件要实现哪些特定的能力,后续系列中将逐步展开构建一个完整的 DSL 具体需要哪些插件及其实现方案,这里我想展开思考的是怎样设计一个比较通用的 DSL 插件方案。

    论述中对插件的定义与 AOP 的思想相当吻合,也当首选使用 AOP 来实现,但这其中还存在一个问题,我希望插件只专注其自身职责的表达,至于哪些节点需要接入哪些插件应当在 DSL 中配置(即我期望插件与 DSL 之间只存在配置关系),而配置应当支持动态更新,因此这就导致了 AOP 的代理对象事先是不确定的,需要去动态生成。

    最后落到实现上,插件这块我需要去攻克两个核心技术点:
    1、怎样去更新 AOP 的代理及切点表达式?
    2、怎样去更新 IOC 容器?

    3. 实现方案

    3.1 配置入口

    若不考虑动态更新,那么入口要实现的基本功能有两个:1、按需引入,这很简单,用一个 Conditional 即可;2、加载一个表达式类型的通知器,示例如下:

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

    测试:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DefaultTest {
    
        @ExtensionNode
        private Engine engine;
    
        @Test
        public void test() {
            DslUtils.setDslA();
            engine.launch();
        }
    }
    

    3.2 监听 DLS 变更

    怎么监听配置的更新是所有的配置中心都需要去深入设计的(后续系列中探讨),此处暂用伪代码代替:

    @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 重新执行构建,示例如下:

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

    正如预期效果:

    结语

    灵明无著,物来顺应,未来不迎,当下不杂,既过不恋~ 请关注公众号:

    作者:捷义
    出处:http://www.cnblogs.com/youclk/
    说明:转载请标明来源和作者
  • 相关阅读:
    css中属性继承性总结
    CSS3属性transform(变形),transition(过渡),animation(动画)
    js中的addEventListener
    CSS换行:word-wrap、word-break和text-wrap差别以及 控制文本行数
    js取变量名的规则
    瀑布流布局及其原理
    利用chrome浏览器调试js断点
    return用法
    行内标签和块级元素有哪些
    2019年思岚科技第三季度大事记
  • 原文地址:https://www.cnblogs.com/youclk/p/10094574.html
Copyright © 2011-2022 走看看