Using annotations with values

25 views
Skip to first unread message

Chris Conrad

unread,
Apr 30, 2007, 11:08:07 PM4/30/07
to google-guice
I've been looking into using annotations with values and I can't seem
to figure out a clean way to do this. Basically what I want is
something like this:

class foo {
@Inject @SomeResourceLikeThing("pathToTheResource") SomeResource
resource;
}

More or less I want to be able to inject a resource (think a file,
Lucene index, etc) where the value of the annotation points at the
resource to inject. I've come up with three possible solutions and I
like the last one the best:

1. I could inject a builder type object and build the resource that
way.

2. I could write some code like
configure() {
binder.bind(SomeResource.class).annotatedWith(new
NamedAnnotation("pathToTheResource"))
.toProvider(new
SomeResourceLikeThingProvider("pathToTheResource"));
}

but that has several problems. Besides being very verbose, I need to
do that for each and every resource and SomeResourceLikeThingProvider
is no longer "in the club" (not to mention that now
"pathToTheResource" appears in at least three different places).

3. What I'd really like is to write something like
configure() {

binder.bind(SomeResource.class).annotatedWith(SomeResourceLikeThing.class)
.toProvider(SomeResourceLikeThingProvider.class));
}

where the value of the annotation is passed in to the provider at
runtime or, in the alternative, the provider is given a handle to the
Field/Method/Constructor being injected so I can look up the value on
my own.

Am I approaching this in entirely the wrong direction, is anything
like this currently possible or am I going to have to wait for the
result of the customized @Inject/post processor debate and whatever
feature that results in 1.1?

Thanks,
--Chris

Dhanji R. Prasanna

unread,
May 1, 2007, 3:30:41 AM5/1/07
to google...@googlegroups.com
Ok this is really getting into hacking territory now. Guice uses annotations to denote wiring hooks within the code only.
Imo, guice does not encourage configuration data within annotations. What you want is *much* better in module code, either as you suggested with multiple bindings or better, multiple keys and providers:

@Inject @SomeResourceAtPathA SomeResource res;

//module
bind(SomeResource.class).annotatedWith(SomeResourceAtPathA.class).toProvider(SomeResourceAtPathAProvider.class);

Then in the provider:
class SomeResourceAtPathAProvider impls Provider<SomeResource> {
    @Inject @Named("pathA") String pathA;
   //etc.
}

Using the annotation's value to specify an instance (in this case of String) is no different to:
@Inject @MyInject(MyInstanceProvider.class) Service service;

The point Im trying to make is that the instance binding belongs in module or Provider code not in metadata (then it becomes difficult to change the impls for testing/redeployment etc.).


Having said that however, you can hack your way to your solution (and I *do* mean hack) by splitting up your provider in this unholy manner:

bind(SomeResource.class ).annotatedWith(new NamedAnnotation("path")).to(SomeResourceProvider.class).in(ABUSE_SCOPE);

//should never be used!
final Scope ABUSE_SCOPE = new Scope() {
    public <T> Provider<T> scope(Key<T> key, final Provider<T> creator) {
         if (!NamedAnnotation.class.equals(key.getAnnotationType())
               throw new RuntimeException();

         final String path = ((NamedAnnotation)key.getAnnotation()).value();

         //wrap provider with "path" logic...
         return new Provider<T> {
               public T get() {
                   T t = creator.get();
                   //configure path here...
                   return t;
               }
         };
    }
}

Provider interceptors/post-processors will make this a lot easier/saner. However I stick to my original guns that this is a *bad* way to use guice (DI in general) and provide the hack above to discourage (hopefully) this type of configuration; *even with* post-processors for the reasons Ive mentioned above. Those are my 2c.

Dhanji.

Ray Tayek

unread,
May 1, 2007, 3:44:15 AM5/1/07
to google...@googlegroups.com
At 08:08 PM 4/30/2007, you wrote:

>I've been looking into using annotations with values ...
> @Inject @SomeResourceLikeThing("pathToTheResource") SomeResource ...

you probably know this, but just in case, junit4 does something like this with:


@Test(expected= IndexOutOfBoundsException.class) public void empty() {
new ArrayList<Object>().get(0);
}

thanks

---
vice-chair http://ocjug.org/


Chris Conrad

unread,
May 1, 2007, 1:40:14 PM5/1/07
to google-guice
I tried to post a response last night and it appears to have never
made it. Oh well, I hope I remember what I was going to say.

In general I think I agree with what you're saying, however I think I
may have been too general and not explained my use case correctly. I
think that because I don't understand how this comment applies to what
I want to do:

"The point Im trying to make is that the instance binding belongs in
module
or Provider code not in metadata (then it becomes difficult to change
the
impls for testing/redeployment etc.). "

To be very concrete with an example, imagine I'm adding search support
to an application. I'm using Lucene as my underlying IR library and
there are twelve distinct indexes I'm going to need to support all my
use cases. What I'd like to see in my code is something like

class UserSearchController {
@Inject @LuceneSearch("/indexes/users") Search userSearch;
}

class FooSearchController {
@Inject @LuceneSearch("/indexes/foo") Search fooSearch;
}

etc.

I follow your point with having an annotation and provider for each
case but in my mind that doesn't really seem to scale. In my use case
I'd need 12 annotations and 12 providers and, for all intents and
purposes, the only difference between the providers is a string
constant. The SpringIntegration basically suffers from exactly this
same problem, it can just hide it since getting a list of spring beans
is possible at runtime so it generates all the bindings in code. If,
in order to use SpringIntegration, you needed to create an annotation
+ provider per bean I'd say that it is useless in practice. Even
needing to hand create a binding to a named annotation for each
possible spring bean can quickly become problematic (think of an
application with a hundred or two spring beans, your module code would
become ridiculously long). For either my use case or the
SpringIntegration use case, however, if the provider got access to the
value in the annotation then there would only need to be a single
binding and a single provider. The provider could use the metadata in
the annotation to respond to the get method.

I don't see how I am making testability any harder since I'm not
depending on any concrete classes in my code, the provider still
creates the concrete instance, it just uses a piece of metadata to
make it's job a little easier.

Lastly, I'm not claiming that there isn't an easier or more Guicey way
of dealing with this, I'm just trying to find out what people who know
more than me think. It seems that by supporting values in annotations
at all that Guice is getting us most of the way there, it's just this
last piece that I'm not seeing how to do cleanly.

Thanks for your input
--Chris

On May 1, 12:30 am, "Dhanji R. Prasanna" <dha...@gmail.com> wrote:
> Ok this is really getting into hacking territory now. Guice uses annotations
> to denote wiring hooks within the code only.
> Imo, guice does not encourage configuration data within annotations. What
> you want is *much* better in module code, either as you suggested with
> multiple bindings or better, multiple keys and providers:
>
> @Inject @SomeResourceAtPathA SomeResource res;
>
> //module
> bind(SomeResource.class).annotatedWith(SomeResourceAtPathA.class
> ).toProvider(SomeResourceAtPathAProvider.class);
>
> Then in the provider:
> class SomeResourceAtPathAProvider impls Provider<SomeResource> {
> @Inject @Named("pathA") String pathA;
> //etc.
>
> }
>
> Using the annotation's value to specify an instance (in this case of String)
> is no different to:
> @Inject @MyInject(MyInstanceProvider.class) Service service;
>
> The point Im trying to make is that the instance binding belongs in module
> or Provider code not in metadata (then it becomes difficult to change the
> impls for testing/redeployment etc.).
>
> Having said that however, you can hack your way to your solution (and I *do*
> mean hack) by splitting up your provider in this unholy manner:
>
> bind(SomeResource.class).annotatedWith(new NamedAnnotation("path")).to(

Kevin Bourrillion

unread,
May 1, 2007, 5:40:16 PM5/1/07
to google...@googlegroups.com
Hey Chris,

Once I finish this:

http://code.google.com/p/google-guice/issues/detail?id=27&can=2

then you will do either this:

  class LuceneSearchProvider extends ContextualProvider<Search> {
    public Search get(InjectionPoint injectionPoint) {
      LuceneSearch annotation
          = (LuceneSearch) injectionPoint.getBindingAnnotation();
      String name = annotation.value();
      // voila!
    }
  }

or this:

  class LuceneSearchProvider implements Provider<Search> {
    @Inject InjectionPoint injectionPoint;

    public Search get() {
      LuceneSearch annotation
          = (LuceneSearch) injectionPoint.getBindingAnnotation ();
      String name = annotation.value();
      // voila!
    }
  }

There are a few issues about this, and of course I'd like to remove the need for that cast.

I wonder what other pieces of information people believe should be accessible from the InjectionPoint.  A collection of all the annotations, perhaps?  We can provide the Member that's being injected, but I don't know why anyone would need that and I don't want it to be only an invitation for abuse.

K

Dhanji R. Prasanna

unread,
May 1, 2007, 8:30:16 PM5/1/07
to google...@googlegroups.com
On 5/2/07, Chris Conrad <rha...@gmail.com> wrote:

I tried to post a response last night and it appears to have never
made it.  Oh well, I hope I remember what I was going to say.

=)


To be very concrete with an example, imagine I'm adding search support
to an application.  I'm using Lucene as my underlying IR library and
there are twelve distinct indexes I'm going to need to support all my
use cases.  What I'd like to see in my code is something like

class UserSearchController {
    @Inject @LuceneSearch("/indexes/users") Search userSearch;
}

class FooSearchController {
    @Inject @LuceneSearch("/indexes/foo") Search fooSearch;
}

My problem with this is that "/indexes/foo" is really instance data. It is the semantic equivalent of:

@Inject @Provide(new MyServiceImpl()) Service serv;

...if that were possible (which it is with primitives and strings). The objection is that you are tightly coupling your client code to the deployment (read impl) environment. If you installed the app on another machine that had indexes at "/var/indexes/foo" you have to dig thru all ur code and rewrite every little bit, then rebuild the app.

Whereas with externalized bindings, you can stick this in a props file for instance. The same applies to testing, where you can simply drop in a different Module() and test everything in "/tmp/index" or something. In a broader sense, this is very important, especially in any sort of production code that is published to more than one deployment.

I follow your point with having an annotation and provider for each
case but in my mind that doesn't really seem to scale.

Chris, I take your point that in this particular case it would make your life easy, but I would urge
you to consider the benefits aforementioned of the alternative. =)


If,
in order to use SpringIntegration, you needed to create an annotation
+ provider per bean I'd say that it is useless in practice.  Even
needing to hand create a binding to a named annotation for each
possible spring bean can quickly become problematic (think of an
application with a hundred or two spring beans, your module code would
become ridiculously long).

I see it differently, in the case of spring, props files, hivemind services, etc., we are trying
to retrofit a string-based indexing scheme into guice's index-less (id-less) environment.

All you need is an injection point marker saying hey I need this type of service. Later, a component
assembler provides you with the service in the module -- hey I am contributing service impl X for this type.
Guice wires them up, the two are completely decoupled.


  For either my use case or the
SpringIntegration use case, however, if the provider got access to the
value in the annotation then there would only need to be a single
binding and a single provider.  The provider could use the metadata in
the annotation to respond to the get method.


I dont see how using @Named("spring_id") to retrieve a spring bean requires a provider binding around each bean description *or* one that receives info around each injection point. The spring integration simply says, I provide this instance to all @Named("spring_id") occurrences. That is the approach I suggest with your use case too.

 I'm just trying to find out what people who know
more than me think.  It seems that by supporting values in annotations
at all that Guice is getting us most of the way there, it's just this
last piece that I'm not seeing how to do cleanly.

I fear that putting *instance* configuration data in an annotation is both dangerous in terms of coupling, and leads us down the path of spring-like xml. Guice's use of annotations are *not* simply as a java-replacement for the xml descriptors (compare with a project called spring-annotations), but rather a structural wiring hook embedded in the source code to tell *where* the dependency belongs not *what* the dependency is.

I hope I am making sense. Perhaps I am being too pedantic and an exception is justified in your case, however as said before there isnt a way to do it right now without a hack. =)

Dhanji.


Laura Werner

unread,
May 1, 2007, 11:50:32 PM5/1/07
to google-guice
Hi Kevin,

> LuceneSearch annotation
> = (LuceneSearch) injectionPoint.getBindingAnnotation();

Are you allowed to have multiple annotations on an injection? The
language allows that syntactically, but I don't know if Guice does
anything to detect and prevent it. If not, you might need to change
the API to look more like:
LuceneSearch annotation =
injectionPoint.getBindingAnnotation(LuceneSearch.class);

Actually that might be preferable anyway because it's more type safe.

-- Laura

Kevin Bourrillion

unread,
May 2, 2007, 12:02:31 AM5/2/07
to google...@googlegroups.com
Ah yes, so, I was thinking to make all the annotations available, but have a special shortcut for getting the one that is the binding annotation.  However, maybe that is pointless, since what you describe is a simple enough way to get any annotation whether it's the binding annotation or not.

One thing that's a little weird is that an injection point can be either a field or a *parameter*, and if it's a parameter you may have annotations on both the parameter itself and on the method/constructor.  I was considering simply union-ing those together for purposes of the InjectionPoint interface, figuring that your custom provider should hardly be one to care which of the two a particular annotation was on.

But it is a little odd.

K

avrecko

unread,
May 2, 2007, 4:25:03 PM5/2/07
to google-guice
Chris, you present a perfectly valid use case. I am doing something
similar but with method interception. Extra metadata is sometimes
needed. I am doing

@Authorized (roles="boss")
public interceptorProtectedMethod(...){
... hireSomePeople...:)
}

and in the Interceptor

public Object invoke(MethodInvocation i) throws Throwable {
... i.getMethod().getAnnotations() // do the magic}

InjectionPoint looks cool, but maybe Annotation[] would do the trick?
Something like

interface Provider<T>{

T get(Annotation[] annotations);}

then you can do

@Inject
@ExtraAnnotation (filename="test.txt")
private File file;

and then in FileProvider implements Provider<File>
File get(Annotation[] annotations){

ExtraAnnotation extra = ... get from annotations;
return new File(extra.filename());}

Normally you would just ignore the Annotation[], just my 2 cents.

Reply all
Reply to author
Forward
0 new messages