Search code examples
groovyclosuresguice

Why doesn't a Groovy closure have access to injected class member?


We are using Groovy and Guice on a project and I came across the following error:

groovy.lang.MissingPropertyException: No such property: myService for class: com.me.api.services.SomeService$$EnhancerByGuice$$536bdaec

Took a bit to figure out, but it was because I was referencing a private class member, that was injected, inside of a closure. Can anyone shed any light as to why this happens?

Furthermore, is there any better way of doing this?

Here is a snippet of what the class looks like:

import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class MyService extends BaseService<Thing> {

    @Inject
    private ThingDao thingDao

    @Inject
    private OtherService<Thing> otherService

    @Override
    List<Thing> findAll() {
        List<Thing> things = this.dao.findAll()

        things.each { 
            //Note: This doesn't work!
            otherService.doSomething()
        }

        things
    }

I either have to use a standard for loop or not use the injected member which then tends to lead to code duplication.


Solution

  • TLDR;

    Either declare otherService public (remove private modifier) or add a getter OtherService<Thing> getOtherService(){otherService}

    If you absolutely want to avoid exposing the field through a property, you can do the following trick: create a local variable outside the Closure scope that references your service:

    OtherService<Thing> otherService=this.otherService
    things.each { 
            //Note: This will work! Because now there is a local variable in the scope. 
            //This is handled by normal anonymous inner class mechanisms in the JVM.
            otherService.doSomething()
    }
    

    Explanation

    Under the hood, your closure is an object of an anonymous class, not the object that has your private field, otherService.

    This means that it can't resolve a direct reference to the field. Accessing a symbol inside the closure will first look at local variables, and if no match is found, the getProperty() method in Closure will be called to find a property, depending on the resolution strategy that you defined. By default, this is OWNER_FIRST.

    If you look the code of Closure#getProperty:

            switch(resolveStrategy) {
                case DELEGATE_FIRST:
                    return getPropertyDelegateFirst(property);
                case DELEGATE_ONLY:
                    return InvokerHelper.getProperty(this.delegate, property);
                case OWNER_ONLY:
                    return InvokerHelper.getProperty(this.owner, property);
                case TO_SELF:
                    return super.getProperty(property);
                default:
                    return getPropertyOwnerFirst(property);
            }
    

    You see that the owner, delegate and declaring objects need to have matching properties.

    In groovy, if you declare a field private, you won't get auto-generated accessor methods, so no properties will be publicly exposed for outside objects.