Parametrized provider

69 views
Skip to first unread message

Aleksey Didik

unread,
Apr 8, 2009, 4:18:41 AM4/8/09
to guiceyfruit
Hello James,
I want to have your advise about one idea.

First, what I want to do.

I have some registry singleton in my app which contains all necessary
properties for all parts of app.

class Registry {

String get(String property) {
//stub
return "Cuska";
}
}

Some components of my app need Registry to work (is component enabled,
connection timeout and etc), but I don't want to inject Registry
itself, it will make components harder to test and not only. I want to
inject properties values from Registry :)

I have found Names.bindProperties() solution, but it's not for me;
properties in Registry is not immutable, and I can't bind all of them
in module and use @Named after.

When I looked your solution about @Resource yesterday, I found that we
can use custom annotations and annotations providers only for fileds
and methods (please, tell me if I not right).

That means, I can make something like this:

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@BindingAnnotation
static @interface Property {
String value();
}
/------------------------------------------------

class PropertyProvider implements
AnnotationMemberProvider<Property> {

@Inject
Registry registry;

public Object provide(Property annotation, TypeLiteral<?>
type, Field field) {
return registry.get(annotation.value());
}

public Object provide(Property annotation, TypeLiteral<?>
type, Method method, Class<?> parameterType, int parameterIndex) {
return registry.get(annotation.value());
}

public boolean isNullParameterAllowed(Property annotation,
Method method, Class<?> parameterType, int parameterIndex) {
return false;
}
}
/------------------------------------------------
class Foo {

@Property("foo-name")
private final String name;

}

That ok, but what about constructor injection?

class Foo {
private final String name;

@Inject
public Foo(@Property("foo-name") String name) {
this.name = name;
}

}

Custom annotations will not work in this case.

But I need to have constructor injection...

Ok, my proposal is KeyedProvider.

interface KeyedProvider<T> {
String get(Key<T> key);
}

//----------------------------

class PropertyProvider implements KeyedProvider<String> {

@Inject
Registry registry;

public String get(Key<String> key) {
final Property propertyAnnotation = (Property) key.getAnnotation
();
return registry.get(propertyAnnotation.value());
}
}

//--------------------------

Module module = new AbstractModule() {
protected void configure() {
bind(String.class).annotatedWith
(Property.class).toKeyedProvider(new PropertyProvider());
}
};

As to me, it will be very usefull. We can use binding annotations not
only as markers, but as a set of parameters, as what and how provide
something. But before post it in google-groups, I want to catch your
advise. May be I have lost something important about guice
possibilities. What do you think about it?


Best regards,
Aleksey.




James Strachan

unread,
Apr 8, 2009, 6:02:32 AM4/8/09
to guice...@googlegroups.com
2009/4/8 Aleksey Didik <alekse...@gmail.com>:

> Hello James,
> I want to have your advise about one idea.
>
> First, what I want to do.
>
> I have some registry singleton in my app which contains all necessary
> properties for all parts of app.
>
> class Registry {
>
>      String get(String property) {
>           //stub
>           return "Cuska";
>      }
> }
>
> Some components of my app need Registry to work (is component enabled,
> connection timeout and etc), but I don't want to inject Registry
> itself, it will make components harder to test and not only. I want to
> inject properties values from Registry :)
>
> I have found  Names.bindProperties() solution, but  it's not for me;
> properties in Registry is not immutable, and I can't bind all of them
> in module and use @Named after.
>
> When I looked your solution about @Resource yesterday, I found that we
> can use custom annotations and annotations providers only for fileds
> and methods (please, tell me if I not right).

Yes; Guice does not yet have a hook so we can choose a different
constructor yet & invoke it from an extension. So only custom
field/method injections are possible right now.


> That means, I can make something like this:
>
>    @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
>    @BindingAnnotation
>    static @interface Property {
>        String value();
>    }
> /------------------------------------------------
>
>    class PropertyProvider implements
> AnnotationMemberProvider<Property> {
>
>        @Inject
>        Registry registry;
>
>        public Object provide(Property annotation, TypeLiteral<?>
> type, Field field) {
>            return registry.get(annotation.value());
>        }
>
>        public Object provide(Property annotation, TypeLiteral<?>
> type, Method method, Class<?> parameterType, int parameterIndex) {
>            return registry.get(annotation.value());
>        }
>
>        public boolean isNullParameterAllowed(Property annotation,
> Method method, Class<?> parameterType, int parameterIndex) {
>            return false;
>        }
>    }
> /------------------------------------------------
>    class Foo {
>
>        @Property("foo-name")
>        private final String name;
>
>    }

Yes, that would work perfectly for field & method injection.

(BTW in trunk the API has changed a little bit from 2.0-beta-5 for the
AnnotationMemberProvider (mostly in the
AnnotationMemberProviderSupport class to be honest) to deal with all
the generics options on injection.


> That ok, but what about constructor injection?
>
> class Foo {
>    private final String name;
>
>    @Inject
>    public Foo(@Property("foo-name") String name) {
>      this.name = name;
>    }
>
>  }
>
> Custom annotations will not work in this case.

Yeah - I'm gutted we can't get constructor injection working with
custom annotations yet in Guice. Though its taken months of effort
just to get this far, so I'm kinda grateful we got some hooks in Guice
at last :)


> But I need to have constructor injection...
>
> Ok, my  proposal is KeyedProvider.
>
>  interface KeyedProvider<T> {
>    String get(Key<T> key);
>  }
>
> //----------------------------
>
>  class PropertyProvider implements KeyedProvider<String> {
>
>    @Inject
>    Registry registry;
>
>    public String get(Key<String> key) {
>      final Property propertyAnnotation = (Property) key.getAnnotation
> ();
>      return registry.get(propertyAnnotation.value());
>    }
>  }
>
> //--------------------------
>
>    Module module = new AbstractModule() {
>      protected void configure() {
>        bind(String.class).annotatedWith
> (Property.class).toKeyedProvider(new PropertyProvider());
>      }
>    };

I've been having some similar thoughts lately.

A binding annotation can have values; if its got values you might want
to associate a specific set of values to a specific provider; or you
might want to bind all instances of the binding annotation to a kind
of provider - which is given the exact values of the annotation so it
can use those to create the value.

I wonder if Guice could be patched so that the InjectionPoint can be
injected into a provider?

using your original example

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@BindingAnnotation
static @interface Property {
String value();
}

you might want to do

bind(String.class).annotatedWith(Property.class).toProvider(MyPropertyLookup.class);

public clas MyPropertyLookup implements Provider<String> {
@Inject
InjectionPoint injectionPoint;
@Inject
Registry registry;

String get() {
Property property =
injectionPoint.getMember().getAnnotation(Property.class);
return registry.lookup(property.value());
}
}

That way there's no new APIs for Guice - its just that we'd need to
patch Guice to be able to inject the InjectionPoint thats about to be
injected into a provider? i.e. processing InjectionPoint in a special
way - like Injector.

The great thing is this would then allow regular Guice binding
annotations to have custom factories associated with them - or you
could still bind a specific instance of the annotation to a binding
using the regular approach.

BTW I hacked up a little test case to see if this even worked with
current Guice - but unfortunately it doesn't :(
http://code.google.com/p/guiceyfruit/source/browse/trunk/guiceyfruit-core/src/test/java/org/guiceyfruit/experiments/InjectionPointInjectTest.java

--
James
-------
http://macstrac.blogspot.com/

Open Source Integration
http://fusesource.com/

James Strachan

unread,
Apr 8, 2009, 6:44:25 AM4/8/09
to guice...@googlegroups.com
2009/4/8 James Strachan <james.s...@gmail.com>:

Injecting the Key would be fine too BTW :) as the key should have the
Annotation instance on it. I figured InjectionPoint might be useful
too - for example you might wanna use the field name as a default
property value if value is null.

Maybe injecting either?

Aleksey Didik

unread,
Apr 8, 2009, 10:28:20 AM4/8/09
to guiceyfruit
I have wrote simple realisation of your suggestion in
com.google.inject.BoundProviderFactory:

class BoundProviderFactory<T> implements InternalFactory<T>,
CreationListener {

...

public void notify(Errors errors) {
// try {
// providerFactory = injector.getInternalFactory(providerKey,
errors.withSource(source));
// } catch (ErrorsException e) {
// errors.merge(e.getErrors());
// }
}

public T get(Errors errors, InternalContext context, final
Dependency<?> dependency) throws ErrorsException {
errors = errors.withSource(providerKey);

final InjectorImpl childInjector = (InjectorImpl)
injector.createChildInjector(new AbstractModule() {
protected void configure() {
bind(InjectionPoint.class).toInstance
(dependency.getInjectionPoint());
bind(Key.class).toInstance(dependency.getKey());
}
});
Provider<? extends T> provider = childInjector.getInternalFactory
(providerKey, errors.withSource(source)).get(errors, context,
dependency);

try {
return errors.checkForNull(provider.get(), source,
dependency);
} catch(RuntimeException userException) {
throw errors.errorInProvider(userException).toException();
}
}

...
}
but one problem has appeared:

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

1) Binding to core guice framework type is not allowed: Key.
at com.google.inject.BoundProviderFactory$1.configure
(BoundProviderFactory.java:61)

1 error
at
com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist
(Errors.java:332)
at com.google.inject.InjectorBuilder.initializeStatically
(InjectorBuilder.java:152)
at com.google.inject.InjectorBuilder.build(InjectorBuilder.java:105)
...

Ok, Key can't be binded, but InjectionPoint can, and your test is
works with my simple Guice patch.

That means we could use parametrized annotation for fields, because
for field an injectionPoint.getMember is this field itself.

But this solution don't work with constructors and methods, because
for constructor injectionPoint.getMember is java.reflect.Constructor,
for method - java.reflect.Method.
But @Property annotation is for constructor and method parameters and
InjectionPoint not contains information about currently resolving
dependency, we know where we inject now, but we don't know what.

That means, we need Key :)

But Key couldn't be binded)

Ok, let's use trick and bind not Key.class, but any Key wrapper;

public class KeyProvider {
private final Key key;

public KeyProvider(Key key) {
this.key = key;
}

public Key get() {
return key;
}
}

and bind it instead of Key. With this trick all work fine.

But I'm not very happy.

1) New child injector on every injection. We need to bind new Key and
Injection point for every Dependency. Not very fast.
2) We could use only binding to a provider class, not to an instance.
It could be not so flexible.
3) Any Key wrapper is an ugly hack.



H-m-m-m, the truth is out there.



On Apr 8, 3:44 pm, James Strachan <james.strac...@gmail.com> wrote:
> 2009/4/8 James Strachan <james.strac...@gmail.com>:
>
>
>
> > 2009/4/8 Aleksey Didik <aleksey.di...@gmail.com>:
> >http://code.google.com/p/guiceyfruit/source/browse/trunk/guiceyfruit-...

Aleksey Didik

unread,
Apr 8, 2009, 10:38:49 AM4/8/09
to guiceyfruit
My patches for current google trunk in groups files:

keyedProvider.patch it's my first solution with KeyedProvider,

injectionPointAndKeyBinding.patch it's realization for your solution
(with KeyedProviderTest)
Reply all
Reply to author
Forward
0 new messages