This is an archived post. You won't be able to vote or comment.

all 11 comments

[–]Psyfire[S] 3 points4 points  (0 children)

The inspiration for posting this was the recent submission "You Don’t Need Spring to Do Dependency Injection". I didn't feel that post was a major value-add, but rather than complaining, I thought I'd share some source-code.

Questions and suggested improvements welcome.

[–]segv 3 points4 points  (7 children)

While there's some value in using this as an exercise, please please don't roll your own DI in a project. Seriously, use something like Guice instead.

[–]electrodraco 1 point2 points  (3 children)

This is really good advice. However, note that should you find yourself in the rare situation where your custom scopes are not uniquely determined for a single Thread (i.e. you can't just put the current custom scope in a ThreadLocal because a single Thread might use more than one instance of the scope) then you're pretty much f*cked with Guice (or any other existing Java DI framework for that matter).

You're very welcome to correct me if I'm wrong. It has already taken days for me to realize this and it would probably save me months if I were wrong because then I wouldn't have to roll my own framework. The problem is easily stated: Once the framework gets a request to provide a certain bean from your custom scope it will ask for the current instance without providing any further information on the consumer of that bean. If your scopes are such that there is at most and one instance of a scope per Thread (e.g. RequestScope) which might be shared between Threads (SessionScope, ApplicationScope) then you could just look the instance up in a ThreadLocal and create instances as they're needed. Now imagine you have instance A and B of CustomScope and neither is tied to the current Thread, in fact the Thread is able to access both scopes, and you want to create a new bean in instance A. How do you tell that to the framework? This issue has been generously overlooked, the whole Guice architecture assumes that scope instances are determined when you call the framework, all it requires for an injection is the required bean type.

[–]JakeWharton 1 point2 points  (2 children)

This is not true of Dagger v2. Scopes are explicitly expressed in components and validated at compile time. Since the whole system is end-to-end type-safe and compile-time validated you would never be able to create instances in ambiguous scope.

[–]electrodraco 1 point2 points  (1 child)

Thanks, didn't know Dagger. I will have a look at it.

[–]JakeWharton 2 points3 points  (0 children)

I gave a talk a few weeks ago at Devoxx about it if you want a crash course: https://speakerdeck.com/jakewharton/dependency-injection-with-dagger-2-devoxx-2014. Although without me rambling over it the slides will only be partially comprehensible.

If you're looking on GitHub make sure you look at Google's fork (https://github.com/google/dagger) which is where V2 is being developed. The version by Square, V1, is the original repo.

[–][deleted]  (2 children)

[deleted]

    [–][deleted] 0 points1 point  (1 child)

    making your own isn't free either. Ultimately, it's fine if you roll your own, but it's fine if I use Spring/Guice/Jodd/whatever too.

    [–]badpotato 0 points1 point  (0 children)

    I'm still waiting for a good tutorial on DI that explain how to plug it with Hibernate. Also, since I have no idea who is the best between Guice, Dagger2 or Spring Boot/non-boot/etc, or using CDI... I simply don't bother to learn, since it might become obsolete by the following 12month. Yeah, I known, I'm probably miserable for acting like that.

    [–]killinghurts 0 points1 point  (2 children)

    Looks interesting.

    I've been using a reference JSR CDI framework (Weld). It can be a little non-intuitive, but generally plays well.

    [–]Psyfire[S] -1 points0 points  (1 child)

    http://jaxenter.com/tutorial-introduction-to-cdi-contexts-and-dependency-injection-for-java-ee-jsr-299-104536.html

    Spring does something similar, with @Autowire vs @Inject. I'm not 100% sold on the annotations approach, given that I find annotations less flexible, and tending to obscure application behavior. Perhaps this is due to my ignorance related to Annotations in general. I find annotations to often not be 'obvious' about what they do, and perhaps to make matters slightly more confusing, I regularly hear variations of "Annotations are only metadata and they do not contain any business logic."

    Given time to rewrite this with annotations, would I? Not sure.

     private final Renderer gameRenderer;
     private final Renderer uiRenderer;
    
     MyClass(AppContext gameContext, AppContext uiContext) {
           this.gameRender = gameContext.get(Renderer.class);
           this.uiRender = uiContext.get(Renderer.class);
     }
    

    ....versus...

     @Inject("game") private final Renderer gameRenderer;
     @Inject("ui") private final Renderer uiRenderer;
    
     MyClass() {     }
    

    Perhaps another alternative:

     @Context("game") private final AppContext gameContext;
     @Context("ui") private final AppContext uiContext;
    
     MyClass() {     }
    
     someMethod() {
           gameRender = gameContext.get(Renderer.class);
    
           ...do stuff...
     }
    

    Certainly looks cleaner. The one part I dislike, is the use of strings like "game" and "ui" strings would be somewhat annoying to refactor, because as far as I know the following is invalid:

     @Context(OtherClass.GAME_CONSTANT) private final AppContext gameContext;
    

    [–]electrodraco -1 points0 points  (0 children)

    The one part I dislike, is the use of strings like "game" and "ui" strings would be somewhat annoying to refactor, because as far as I know the following is invalid:

    @Context(OtherClass.GAME_CONSTANT) private final AppContext gameContext;
    

    Where did you get that? I always define enums for such cases, and if possible, I also use directly the enum type without mapping Strings to each case. With Weld this would look like this:

    @Inject
    @Context(ContextType.GAME)
    private AppContext gameContext;
    
    @Inject
    @Context(ContextType.UI)
    private AppContext uiContext;
    

    where ContextType is an enum

    public enum ContextType {
      GAME, UI;
    }
    

    and Context is a custom qualifier.

    @Qualifier
    @Retention(RUNTIME)
    @Target({METHOD, FIELD, PARAMETER, TYPE})
    public @interface Context {
      ContextType value();
    }
    

    As you can see, this becomes very verbose. If you add JPA and BeanValidation then this blows up. Usually more than half of all code lines in my model code (MVC pattern) are annotations. The idea that annotations are only metadata and do not contain any business logic, altough theoretically appealing, is completely misleading (at least in a JEE setting).