Michael Kolakowski
Michael Kolakowski

Reputation: 805

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.

Upvotes: 3

Views: 1009

Answers (1)

Luis Mu&#241;iz
Luis Mu&#241;iz

Reputation: 4811

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.

Upvotes: 5

Related Questions