Spring对Groovy的支持

"spring是怎么实现对Groovy的注入支持的?"

Posted by Ariescat on March 4, 2020

“🙉🙉🙉 ”

前言

使用 Groovy 的原因很简单,因为 Groovy 脚本支持热加载功能。下面来看看 Groovy 如何增加基于 Spring 的应用程序的灵活性。

spring对groovy的支持

  • 自定义GroovyFactory,扫描groovy脚本注册BeanDefinition,注意看这里:

    bd.setBeanClassName(
        "org.springframework.scripting.groovy.GroovyScriptFactory");
    

    也就是最终产生能执行的class是在这个工厂里编译出来的

  • org.springframework.scripting.support.ScriptFactoryPostProcessorspringgroovy的支持的核心基本上在这里

    • 先执行setBeanFactory,这里有一行关键的代码:

      // Required so that all BeanPostProcessors, Scopes, etc become available.
      this.scriptBeanFactory.copyConfigurationFrom(this.beanFactory);
      

      附:

      因为这里拷贝了spring默认的工厂,此后的groovy的构造是由scriptBeanFactory完成的,所以如果想对groovy的代码进行扩展,则必要要在ScriptFactoryPostProcessor执行之前做处理。

      • RpcConsumerProcessor

        如新增一个RpcConsumerProcessorgroovy类注解有@RpcConsumer的字段进行RPC代理注入,则必要提高RpcConsumerProcessor的优先级,可以考虑实现PriorityOrdered,不然的话scriptBeanFactory copy完了是拿不到RpcConsumerProcessor的,更别说执行了

        至于为什么会这样就要看Spring的源码了AbstractApplicationContext#registerBeanPostProcessors

        // First, register the BeanPostProcessors that implement PriorityOrdered.
        OrderComparator.sort(priorityOrderedPostProcessors);
        registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors); // 所以必须要在下面ScriptFactoryPostProcessor构造之前把自定义的Processors优先注册进去
              
        // Next, register the BeanPostProcessors that implement Ordered.
        List<BeanPostProcessor> orderedPostProcessors = new ArrayList<BeanPostProcessor>();
        for (String ppName : orderedPostProcessorNames) {
        	BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); // 这里就会构造ScriptFactoryPostProcessor并调用setBeanFactory
        	orderedPostProcessors.add(pp);
        	if (pp instanceof MergedBeanDefinitionPostProcessor) {
        		internalPostProcessors.add(pp);
        	}
        }
        
      • RpcProviderProcessor

        这个Processor又为什么不需要PriorityOrdered更改优先级呢,因为他不需要获取实例来注入,只需要获取到targetClass就行了,而这个Spring的AopUtils.getTargetClass(bean)就可以完美的获取到代理的Class,从而获取到该Class的字段和方法。

      • 构造完groovy对象后,看spring的doCreateBean

        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
        	populateBean(beanName, mbd, instanceWrapper);
        	if (exposedObject != null) {
        		exposedObject = initializeBean(beanName, exposedObject, mbd);
        	}
        }
        

        这里initializeBean就会触发各种BeanPostProcessors,完成上面自定义的BeanPostProcessors的调用:

        applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        
    • 再看predictBeanTypepostProcessBeforeInstantiation

      这两个方法都是调用到prepareScriptBeans方法,看进去,调用了createScriptedObjectBeanDefinition

      GenericBeanDefinition objectBd = new GenericBeanDefinition(bd); // 传进父bd,也就是一开始我们GroovyFactory构建的bd
      objectBd.setFactoryBeanName(scriptFactoryBeanName);
      objectBd.setFactoryMethodName("getScriptedObject"); // 看到这里是不是就和GroovyScriptFactor对应上啦,之后spring就会走工厂方法把对象构造出来
      objectBd.getConstructorArgumentValues().clear();
      objectBd.getConstructorArgumentValues().addIndexedArgumentValue(0, scriptSource);
      objectBd.getConstructorArgumentValues().addIndexedArgumentValue(1, interfaces);
      

      createScriptedObjectBeanDefinition调用完后,会执行:

      if (refreshCheckDelay >= 0) {
        	objectBd.setScope(BeanDefinition.SCOPE_PROTOTYPE); // 这一步很重要,此后的脚本热替换要用到
      }
      

      附:

      其实这里的scriptFactoryscriptSource没太看懂是干嘛的,以后研究下

      ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class);
      ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator());
      
  • 执行到postProcessBeforeInstantiation,才真正调用createRefreshableProxy

    RefreshableScriptTargetSource ts = new RefreshableScriptTargetSource(this.scriptBeanFactory,
    		scriptedObjectBeanName, scriptFactory, scriptSource, isFactoryBean);
    ...
    return createRefreshableProxy(ts, interfaces, proxyTargetClass);
      
    

    createRefreshableProxy这个方法内部,事实上是调用了new JdkDynamicAopProxy(config)创建了一个代理对象。此后调用接口的方法则会这样执行:

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ...
        TargetSource targetSource = this.advised.targetSource; // 这里的targetSource就是RefreshableScriptTargetSource
        ...
        target = targetSource.getTarget(); // 看父类的getTarget
        ...
        // 接下来就是反射调用了
    }
      
    

    附:

    其实这里还有一个坑:groovy注册进spring,但没地方引用,启动的时候是不会进postProcessBeforeInstantiation这个方法的。虽然以后调用getBean会重新进来,但如果是想在启动的时候做一些工作,则要注意这一点。

  • getTarget是怎么拿到实例化的groovy对象呢?看GroovyScriptFactory的源码getScriptedObject

    this.scriptClass = getGroovyClassLoader().parseClass(scriptSource.getScriptAsString(), scriptSource.suggestedClassName()); // 这里面把groovy编译为字节码,并装载进虚拟机
      
    GroovyObject goo = (GroovyObject) scriptClass.newInstance();
    

    再深层一点就不解读了,涉及到groovy的编译了,有兴趣可以去了解:

    1. org.codehaus.groovy.runtime.callsite.CallSite
    2. invokedynamic指令

    这里我也测试了一些基础的javagroovy的结合使用:

    study-metis -> GroovyClassLoaderApp.java

  • 脚本是如何刷新的?

    其实上面的流程已经出现了一个关键:RefreshableScriptTargetSource,在getTarget的时候会判断refreshCheckDelayElapsed(),若有修改并且符合了refreshCheckDelay时间,就会走refresh()重新beanFactory.getBean(beanName),又因为这里的BeanDefinitionSCOPEPROTOTYPE,所以最终再次走进GroovyScriptFactory»parseClass »newInstance

最后总结spring的getBean()流程

getBean(type) > doGetBean() >

RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); // 这里mbd的beanClassName为GroovyScriptFactory,并且默认为单例

createBean() > doCreateBean() > initializeBean() > ScriptFactoryPostProcessor#postProcessBeforeInstantiation

至此完成Spring最外面的那层代理”壳“,这层“壳”有一个可刷新的RefreshableScriptTargetSource

继续 >

AbstractRefreshableTargetSource#getTargetClass() >

beanFactory.getBean(beanName)

此时beanFactoryscriptBeanFactorybeanNamescriptedObject.XXX.javabdmScopePROTOTYPE >

最终执行进GroovyScriptFactory的工厂方法getScriptedObject() > parseClass() > newInstance

参考


喜迎
春节