本文主要探讨基于 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(); }
正如预期效果:
灵明无著,物来顺应,将来不迎,当下不杂,既过不恋~
个人公众号《捷义》