从 Groovy 闭包访问 Spring 托管 bean 时出现异常

Exception accessing Spring managed bean from Groovy closure

我有一个简单的 Spring 引导应用程序,其中包含 2 个 bean classes、一个主 class 和一个配置 class。每当我尝试从 Groovy 闭包访问 Spring 托管 Bank bean 时,我都会得到一个异常:

Exception in thread "main" java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:304)
    at org.springframework.boot.SpringApplication.runCommandLineRunners(SpringApplication.java:675)
    at com.example.closures.ClosuresApplication$_run_closure1.doCall(ClosuresApplication.groovy:22)
    at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:690)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:957)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:946)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.boot.SpringApplication$run.call(Unknown Source)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:324)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:110)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:130)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:292)
    at com.example.closures.ClosuresApplication.main(ClosuresApplication.groovy:27)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016)
Caused by: groovy.lang.MissingPropertyException: No such property: bank for class: com.example.closures.ClosuresApplication$$EnhancerBySpringCGLIB$735576
    at groovy.lang.Closure.call(Closure.java:423)
    at groovy.lang.Closure.call(Closure.java:439)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2027)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:51)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2012)
    at org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:49)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2053)
    at org.codehaus.groovy.runtime.dgm2.invoke(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:304)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
    at com.example.closures.ClosuresApplication$_run_closure1.doCall(ClosuresApplication.groovy:22)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:110)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:122)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.example.closures.ClosuresApplication.run(ClosuresApplication.groovy:21)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at org.springframework.boot.SpringApplication.runCommandLineRunners(SpringApplication.java:672)
    ... 9 common frames omitted

Bank.groovy

@Component
final class Bank {
    final String name = 'MyBank'
    final AutomatedTellerMachine insideAtm
    final AutomatedTellerMachine outsideAtm

    @Autowired
    Bank(@Qualifier('insideAtm') final AutomatedTellerMachine insideAtm, @Qualifier('outsideAtm') final AutomatedTellerMachine outsideAtm) {
        this.insideAtm = insideAtm
        this.outsideAtm = outsideAtm
    }

    String getName() {
        return name
    }

    AutomatedTellerMachine getInsideAtm() {
        return insideAtm
    }

    AutomatedTellerMachine getOutsideAtm() {
        return outsideAtm
    }
}

AutomatedTellerMachine.groovy

final class AutomatedTellerMachine {
    final String name

    AutomatedTellerMachine(final String name) {
        this.name = name
    }

    String getName() {
        return name
    }
}

AppConfig.groovy

@Configuration
class AppConfig {

    @Bean(name = 'insideAtm')
    AutomatedTellerMachine getInsideAtm() {
        new AutomatedTellerMachine('insideAtm')
    }

    @Bean(name = 'outsideAtm')
    AutomatedTellerMachine getOutsideAtm() {
        new AutomatedTellerMachine('outsideAtm')
    }
}

ClosuresApplication.groovy

@SpringBootApplication
class ClosuresApplication implements CommandLineRunner {

    @Autowired
    private Bank bank

    @Override
    void run(String... args) throws Exception {
        for (def i = 0; i < 10; i++) {
            printf 'Bank %02d: %s%n', (i + 1), bank
        }

        (1..10).each {
            printf 'Bank %02d: %s%n', it, bank
        }
    }

    static void main(String[] args) {
        SpringApplication.run ClosuresApplication, args
    }
}

常规 for 循环工作得很好,但是 Groovy 的 .each {} 闭包给出了一个异常。有什么想法吗?

我有时 运行 遇到这个问题,发现一个简单的解决方法可以解决它 - 在 run 方法中添加对 bank 变量的引用:

def _bank = bank
(1..10).each {
    printf 'Bank %02d: %s%n', it, _bank
}

这似乎是一个奇怪的范围界定问题,我一直无法确定其真正原因。

另一种可能的解决方案是通过删除 'private' 修饰符将 bank 字段转换为 groovy 属性。

 @Autowired
 Bank bank

这在视觉上不那么可怕(尤其是当您对多个字段有此问题时),但它确实会改变该字段的访问级别。

我遇到过很多这样的问题,所有这些问题似乎都与在 spring 管理的 bean 中的各种闭包内使用私有字段有关,特别是当 bean 'enhanced'(代理)由 spring (EnhancerBySpringCGLIB).