Custom scopes: Enumeration and instantiation

84 views
Skip to first unread message

Martin Aspeli

unread,
Jan 23, 2011, 11:44:10 AM1/23/11
to google-guice
Hi,

I'm trying to integrate Guice with JAdapter (http://code.google.com/p/
jadapter), at the same time learning Guice in more depth. However, I'm
getting a little confused. :)

In JAdapter, you write an adapter class like:

class FooBar implements Foo {
public FooBar(Bar context) { ... }
public void foo() {}
}

The idea is that the single-argument constructor takes the adapted
context (a Bar) and implemented the interface being adapted to (Foo).

In code using the adapter registry, one might @Inject a
TransfomerRegistry, and then do:

Foo foo = transformerRegistry.transform(bar, Foo.class);

The adapter registry looks up the most appropriate adapter from bar to
a Foo (FooBar.class in this case) and instantiates it.

I'd also like to support @Inject annotations on a FooBar adapter under
Guice, for example with setters or a void wire() type method, so that
when the JAdapter registry instantiates a FooBar, it is injected (from
the default or singleton scope, at least).

My first attempt at this is to define a custom @AdapterScoped scope.
The idea is that people could do:

// Register adapters
bind(FooBar.class).in(AdapterScoped.class);

// Register the adapter registry, which can then be injected into
other classes
bind(TransformerRegistry.class).to(GuiceAdapterRegistry.class);

I've managed to write the AdapterScoped annotation, and I've read the
docs on custom scopes, but I'm still confused. For example, I don't
understand how/when the SimpleScope is "seeded" (which is not part of
the Scope interface), or whether there's a way to enumerate types in a
scope.

More concretely, the two missing pieces for me are:

- In GuiceAdapterRegistry's constructor, I'd like to iterate over all
objects in the @AdapterScoped scope, to add them to the internal
JAdapter registry.

- In the GuiceAdapterRegistry.transform() implementation, I'd then
like to instantiate the FooBar class using Guice (or imperatively ask
Guice to inject the instance just constructed) so that @Inject is
honoured based on the current Guice configuration. This should also
allow classes in the @AdapterScoped scope to be injected with unscoped
instances or instances in the @Singleton scope.

It's possible I'm barking up the wrong tree here, of course. I also
looked at Multibindings, but since I'm effectively registering classes
here and want to inject each time a class is instantiated as the
result of a transform() call, they don't seem to be a match to me.

Any pointers would be greatly appreciated!

Martin

Martin Aspeli

unread,
Jan 23, 2011, 7:06:04 PM1/23/11
to google-guice
To add to my own questions, I think there are some things I'm just not
understanding about scopes and providers.

Consider the following annotation:

@Target({ TYPE, METHOD })
@Retention(RUNTIME)
@ScopeAnnotation
public @interface AdapterScoped {}

And then a very simple scope:

public class AdapterScope implements Scope {

private Set<Class<?>> classes = new HashSet<Class<?>>();

public <T> Provider<T> scope(Key<T> key, final Provider<T> unscoped)
{
classes.add(key.getTypeLiteral().getRawType());
return new Provider<T>() {
public T get() {
return unscoped.get();
}
};
}

public Set<Class<?>> getClasses() {
return classes;
}

@Override
public String toString() {
return "AdapterScope";
}

}

This is then Guice'd up with:

public class AdapterScopeModule extends AbstractModule {

@Override
protected void configure() {
AdapterScope adapterScope = new AdapterScope();
bindScope(AdapterScoped.class, adapterScope);
bind(AdapterScope.class)
.toInstance(adapterScope);

}

}

I then have a test module that does:

public class SampleModule extends AbstractModule {

@Override
protected void configure() {
bind(SourceToTarget.class)
.in(AdapterScoped.class);
}
}


And a minimal setup:

Injector injector = Guice.createInjector(
new AdapterScopeModule(),
new SampleModule()
);

Now - I see that scope() in the AdapterScope gets called, and it
returns the custom Provider. However, just after that (and before the
Provider inner class is ever called), I get:


com.google.inject.CreationException: Guice creation errors:

com.google.inject.CreationException: Guice creation errors:

1) Could not find a suitable constructor in
org.jadapter.tests.guice.SourceToTarget. Classes must have either one
(and only one) constructor annotated with @Inject or a zero-argument
constructor that is not private.
at org.jadapter.tests.guice.SourceToTarget.class(SourceToTarget.java:
10)
at org.jadapter.tests.guice.SampleModule.configure(SampleModule.java:
12)

1 error
at
com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:
416)
at
com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:
154)
at
com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:
106)
at com.google.inject.Guice.createInjector(Guice.java:95)
at com.google.inject.Guice.createInjector(Guice.java:72)
at com.google.inject.Guice.createInjector(Guice.java:62)
at
org.jadapter.tests.guice.TestGuiceIntegration.testWiring(TestGuiceIntegration.java:
19)


Now - fair enough - there is no @Inject and no zero-arg constructor
for SourcetoTarget. I had expected to somehow use my own Provider to
instantiate it, in fact. But my Provider inner class is never called.

However, if I create a custom Provider in my configuration and do:

bind(SourceToTarget.class)
.toProvider(SomeRandomProvider)
.in(AdapterScoped.class);

Then SomeRandomProvider.get() is not called until I actually try to
look up an instance for SourceToTarget.class.

Why does Guice try to instantiate SourceToTarget using the default
provider instead of using the provider returned by scope() in my
custom scope (AdapterScope)?

Martin
Reply all
Reply to author
Forward
0 new messages