Feature request: allow annotation meta in provider method

71 views
Skip to first unread message

Geoff Groos

unread,
Oct 18, 2015, 1:18:21 AM10/18/15
to google-guice
Hey guys,

So it's occurred to me that a number of classes in our codebase are injecting a so called 'ResourceEnvironment' so they can use it to retrieve a static image from whiten our deployed jar. This got me wondering if I could achieve something as slick as injecting the image directly with guice:

public class SomeService{

 
private final Image image;

 
@Inject
 
public SomeService(@Named("/com/mycompany/myasset.png") myAssetImage){
    image
= myAssetImage;
 
}

 
//big complex methods doing big complex method things.
}

//with a module containing
public class ModuleToDoThat implements Module{
 
public void configure(){
   
//...
 
}
 
 
@Provides Image getImageWithName(Named name){
   
return getClassLoader().getResource(name.value());
 
}
}

This would actually reduce a fair bit of code on our code base; using annotations to pass meta-data about the specific instance that's needed at runtime would be a really cool feather for guice's cap.

AFAIK the feature I'm requesting is binding annotations to be whitelisted (along with the calling Injector) as things that you can request without providing an explicit configuration path for them. A little less obtusely: with the injector you don't need to tell guice how to provide a provider method with an injector, it just assumes the injector you're looking for is the one doing the provide-ing. I would ask that binding annotations be given the same 'scope', such that any binding annotations used would be automatically wired to that provider method (and if they don't exist, the provider methods eligibility for provision removed).

Needless to say this is a fair bit of rope to hang yourself with, in the event that you need to start passing more information than is available at annotation-writing-time, some developers (myself included) might try to ham-fist some things into static scope so that they can be used by the annotation. That said, I think the benefit outweighs the cost; it would be really slick and cut 50 lines of cruft off my codebase.

I'm fairly certain I could hack something together with provision listener and a stack (probably static), but rather than go down that road I figured I should post this as a request-for-help and/or a feature request here first.

Is there a way to do this without using the SPI that would give me the same effect? If I was to try to do it myself with a stack and a provision listener, would it be as hard as I think it is? Is this something I should request a little more formally (by creating a github issue)?

cheers,

-Geoff


Tim Peierls

unread,
Oct 18, 2015, 10:30:06 AM10/18/15
to google...@googlegroups.com
What benefits would this have over what I imagine your colleagues are doing?

public interface ResourceEnvironment {
  Image getImage(String name);
  // ... other resource-providing methods ...
}

public class SomeService {

  private final Image image;

  @Inject public SomeService(ResourceEnvironment env){
    image = env.getImage("/com/mycompany/myasset.png");
  }

  //big complex methods doing big complex method things.
}

//with a module containing 
public class ModuleToDoThat implements Module{
  public void configure(){
    //...
  }
  
  @Provides ResourceEnvironment getResourceEnvironment() {
    return new ResourceEnvironment() {
  @Override public getImage(String name) {
return toImage(getClassLoader().getResource(name));
  }
  Image toImage(URL url) { ... }
  // ... implement other resource-providing methods ...
    };
  }
}

--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice...@googlegroups.com.
To post to this group, send email to google...@googlegroups.com.
Visit this group at http://groups.google.com/group/google-guice.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-guice/882e5b98-a32a-4f5a-a47f-02b4fc8d117b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Geoff Groos

unread,
Oct 24, 2015, 11:43:59 PM10/24/15
to google-guice, t...@peierls.net
the benefit is fairly straight forward: you don't have to inject a `ResourceEnvironment` instance, and we lose a little bit of code duplication by centralizing the place where the resource-loading flow starts and finishes.

That same object is the one that offers the ability to load native code through JNA, so its a fairly scary object. If it becomes too scary we of course have the option of hiving that complexity off into a new class, a `NativeResourceLoader` and the regular `ResourceEnvironment`, but I just thought the ability to push the actual loading of resources out of a constructor and into guice would be handy.

And I would think that this would be a handy feature for deep configuration: if the type isn't a uniquely identifying feature, the ability to make runtime decisions about static configuration through annotations seems really handy and not likely to break much (whose going to be binding instances of annotations to things?) In my case, a static image resource can be identified by a path similar to "/com/ourcompany/whatever/someimage.png".

Tim Peierls

unread,
Oct 25, 2015, 1:26:39 PM10/25/15
to google-guice, t...@peierls.net
On Saturday, October 24, 2015 at 11:43:59 PM UTC-4, Geoff Groos wrote:
the benefit is fairly straight forward: you don't have to inject a `ResourceEnvironment` instance, and we lose a little bit of code duplication by centralizing the place where the resource-loading flow starts and finishes.

I should have used a less "heavy" sounding term than ResourceEnvironment. How about an interface, ProviderOfNamed<T>? Here's my rewrite of your example again, but with the different name:


This is almost identical to your example, but instead of injecting @Named("...") Image and relying on what would be a new feature, it injects ProviderOfNamed<T>, and the desired image is obtained through a call to provider.get(name). The @Provides method is essentially the same.

 
That same object is the one that offers the ability to load native code through JNA, so its a fairly scary object. If it becomes too scary we of course have the option of hiving that complexity off into a new class, a `NativeResourceLoader` and the regular `ResourceEnvironment`, but I just thought the ability to push the actual loading of resources out of a constructor and into guice would be handy.

I don't think you want to describe it as pushing logic into Guice. Better to say that you want to decouple the actual (and common) machinery of loading a resource based on a string name from the places where those resources are used. Both of our examples accomplish that end, but mine does it without needing a new and potentially very complicated enhancement to Guice.

 
And I would think that this would be a handy feature for deep configuration: if the type isn't a uniquely identifying feature, the ability to make runtime decisions about static configuration through annotations seems really handy and not likely to break much (whose going to be binding instances of annotations to things?) In my case, a static image resource can be identified by a path similar to "/com/ourcompany/whatever/someimage.png".

I'm not following this. Are you saying that you'd like to do static code analysis so you can do build-time initialization of configuration data structures? I can sort of see an application for this, but I don't think it's worth introducing a new Guice feature for it. I would do something like this:

class SomeService {

  static final String IMG_NAME = "/com/whatever/...";

  @Preconfigured(IMG_NAME) final Image img;

  @Inject SomeService(ProviderOfNamed<Image> imgProvider) {
    img = imgProvider.get(IMG_NAME);
  }
}

Then a static analysis tool could go through a jar and know from which things need to be "preconfigured", whatever that means.

Geoff Groos

unread,
Oct 26, 2015, 1:06:29 PM10/26/15
to google-guice, t...@peierls.net
This is almost identical to your example, but instead of injecting @Named("...") Image and relying on what would be a new feature, it injects ProviderOfNamed<T>, and the desired image is obtained through a call to provider.get(name). The @Provides method is essentially the same.

you're absolutely correct about this, and I should've been thinking along these lines from square one.


This is almost identical to your example, but instead of injecting @Named("...") Image and relying on what would be a new feature, it injects ProviderOfNamed<T>, and the desired image is obtained through a call to provider.get(name). The @Provides method is essentially the same.

You are correct that it is almost identical --I don't know why I got trapped in the mode of thinking that I needed to inject big monolithic all-loading classes just to load an image, I can (and should) hive that off into nice little interfaces, exactly as you're doing here. This will go a long way to achieving my goal.

I still have minor gripes: In this example SomeService should not depend on a process to load an image, it should depend on the image. Thanks to guice (and java-8) it is not hard to implement that process using a factory (or provider) for production and a lambda for testing. But there are problems with this approach. I would imagine that serialization of a class (SomeService) that had saved a reference to a provider (ProviderOfNamed) would be difficult, and also the life cycle of the provider is implied to be a one-shot, (ie, the provider gets called once and then garbage collected), which is easy to accidentally abuse.

Anyways, I've made my point, and you've given me some ideas. For now you are exactly right, I'll create an interface that is just for loading images, and bind it to the monolithic object we already have for production and, if I keep the image small enough, I'll be able to do something as simple as name -> mock(Image.class); for testing.

-Geoff

Geoff Groos

unread,
Oct 26, 2015, 1:24:44 PM10/26/15
to google-guice, t...@peierls.net
Oh and I should mention that I haven't got any static code analysis tools along these lines.

All I'm suggesting is that there are other use-cases for this besides loading images, DB connections come to mind although there you might want all the typical try-finally ritual typically associated with db connections.

In our particular case we have a graph that we configure. If I could specify paths into that graph for injection it would save me a lot of test setup headache. Consider:

GraphAnalyzingThing(Graph graph){
  nodesOfInterest
= graph.getRoot().resolve("A").flatMap(node -> node.resovle("B"));
}

vs

GraphAnalyzingThing(@Named("/ANodes/BNodes") List<Node> nodes){
  thingsOfInterest
= nodes;
}

//with a provider:
@Provides List<Node> getNodesForPath(Graph graph, Named path){
 
List<String> traversal = path.split("/");
 
List<node> nodes = { graph.getRoot() };
 
for(String pathSection : traversal){
    nodes
= nodes.flatMap(node -> node.resolve(pathSection));
 
}
 
return nodes;
}


The latter is a lot easier to create from a testing environment, and it allows us to centralize some of that traversal logic.
Reply all
Reply to author
Forward
0 new messages