I am a software engineer at Google on the Android project and the creator of the Java testing framework TestNG. When I'm not updating this weblog with various software-related posts or speaking at conferences, I am busy snowboarding, playing squash, tennis, golf or volleyball or scuba diving. Cedric is a DZone MVB and is not an employee of DZone and has posted 90 posts at DZone. You can read more from them at their website. View Full User Profile

Advanced Dependency Injection With Guice

08.23.2012
| 12546 views |
  • submit to reddit

The more I use dependency injection (DI) in my code, the more it alters the way I see both my design and implementation. Injection is so convenient and powerful that you end up wanting to make sure you use it as often as you can. And as it turns out, you can use it in many, many places.

Let’s cover briefly the most obvious scenarios where DI, and more specifically, Guice, are a good fit: objects created either at class loading time or very early in your application. These two aspects are covered by either direct injection or by providers, which allow you to start building some of your object graph before you can inject more objects. I won’t go too much in details about these two use cases since they are explained in pretty much any Guice tutorial you can find on the net.

Once the injector has created your graph of objects, you are pretty much back to normal and instantiating your “runtime objects” (the objects you create during the life time of your application) the normal way, most likely with “new” or factories. However, you will quickly start noticing that you need some runtime information to create these objects, other parts of them could be injected.

Let’s take the following example: we have a GeoService interface that provides various geolocation functions, such as telling you if two addresses are close to each other:

public interface GeoService {
  /**
   * @return true if the two addresses are within @param{miles}
   * miles of each other.
   */
  boolean isNear(Address address1, Address address2, int miles);
}

Then you have a Person class which uses this service and also needs a name and an address to be instantiated:

public class Person {
  // Fields omitted
 
  public Person(String name, Address address, GeoService gs) {
    this.name = name;
    this.address = address;
    this.geoService = gs;
  }
 
  public boolean livesNear(Person otherPerson) {
    return geoService.isNear(address, otherPerson.getAddress(),
        2 /* miles */);
  }
}

Something odd should jump at you right away with this class: while name and address are part of the identity of a Person object, the presence of the GeoService instance in it feels wrong. The service is a singleton that is created on start up, so a perfect candidate to be injected, but how can I achieve the creation of a Person object when some of its information is supplied by Guice and the other part by myself?

Guice gives you a very elegant and flexible way to implement this scenario with “assisted injection”.

The first step is to define a factory for our objects that represents exactly how we want to create them:

public interface PersonFactory {
  Person create(String name, Address address);
}

Since only name and address participate in the identity of our Person objects, these are the only parameters we need to construct our objects. The other parameters should be supplied by Guice so we modify our Person constructor to let Guice know:

@Inject
public Person(@Assisted String name, @Assisted Address address,
    GeoService geoService) {
  this.name = name;
  this.address = address;
  this.geoService = geoService;
}

In this code, I have added an @Inject annotation on the constructor and an @Assisted annotation on each parameter that I will be providing. Guice will take care of injecting the rest.

Finally, we connect the factory to its objects when creating the module:

Module module1 = new FactoryModuleBuilder()
    .build(PersonFactory.class);

The important part here is to realize that we will never instantiate PersonFactory: Guice will. From now on, all we need to do whenever we want to instantiate a Person object is to ask Guice to hand us a factory:

@Inject
private PersonFactory personFactory;
 
// ...
 
Person p = personFactory.create("Bob", new Address("1 Ocean st"));

If you want to find out more, take a look at the main documentation for assisted injection, which explains how to support overloaded constructors and also how to create different kinds of objects within the same factory.

Wrapping up

Let’s take a look at what we did. First, we started with a suspicious looking constructor:

public Person(String name, Address address, GeoService s) {

This constructor is suspicious because it accepts parameters that do not participate in the identity of the object (you won’t use the GeoService parameter when calculating the hash code of a Person object). Instead, we replaced this constructor with a factory that only accepts identity fields:

public interface PersonFactory {
  Person create(String name, Address address);
}

and we let Guice’s assisted injection take care of creating a fully formed object for us.

This observation leads us to the Identity Constructor rule:

 

If a constructor accepts parameters that are not used to define the identity of the objects, consider injecting these parameters.

Once you start looking at your objects with this rule in mind, you will be surprised to find out how many of them can benefit from assisted injection.

 

 

 

 

 

 

 

 

 

 

 

 

 

Published at DZone with permission of Cedric Beust, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Michal Xorty replied on Thu, 2012/08/23 - 3:10am

Very nice

Balint Persics replied on Thu, 2012/08/23 - 3:44am

I think your OOP-style is bad.

GeoService may be a stateless class, since it apparently does not have any state when determining the distance between to Addresses. Making the GeoService class full of static utility methods is certainly not OOP - it is procedural programming.

I think it is better to have the Address class contain a reference to a GeoService (for which GeoService this address is meaningful), and contain a method Address.isNear(Address).

The GeoService is part of the Address' identity: it describes the service for which that Address is defined and meaningful. For example a UK Address is not meaningful for a Russian GeoService.

You are right that the GeoService is not part of the Person's identity. But you missed that the GeoService is part of the Address' identity.  

As a corollary, Person.isNear() may be implemented with address.isNear(otherPerson.getAddress()).

Thomas Wheeler replied on Thu, 2012/08/23 - 9:32am

Maybe this is a contrived example to demonstrate this feature -- but I agree, it is bad OO. I would say both Person and Address are domain objects and should be simple POJOs. When you want to know if one address is near another, I'd probably use something like a LocationService which contains a (possibly injected) GeoService. The LocationService itself would likely live in a controller. I might have methods like locationService.livesNear(Person p1, Person p2), or locationService.getNearbyAddresses(Address a, AddressRepository ar).

Nice to learn a new feature of Guice though!

Thomas

Balint Persics replied on Thu, 2012/08/23 - 9:59am in response to: Thomas Wheeler

I think you are misusing os misinterpreting the term POJO and domain objects.

POJO-s are not value classes: classes without behaviour and contain only data (Data Transfer Objects or DTOs). POJOs are per definitionem Java objects which are not required to extend any other Java object or implement an interface in order to reach a goal. On the other hand, EJB-s (for which Local, Remote interfaces, implementations MUST be created) are not POJO-s. You have to extend classes and implement interfaces which are not beloning to your problem domain and exist only to plug your code into the environment (in this case: the enterprise application). These complex classes interfere whith the S from SOLID: Single Responsibility. 

Domain objects are objects that reflect the OO-style modeling of our problem domain. Again, they are not DTO-s because every object needs semantics (class invariants, collaborators etc). They are not "clever structs". 

The "clever struct" concept clearly violates the core principles of Object Orientation and leads to the bad architecture of anemic domain models. 

Cedric Beust replied on Thu, 2012/08/23 - 10:06am in response to: Balint Persics

GeoService is a singleton, it's not part of the identity of anything.

 You are focusing too much on the example I picked and you are missing the more general point I am making in my description of assisted injection.

 

Pär Eklund replied on Thu, 2012/08/23 - 10:20am

As I wrote (tried to write) on Cedric's blog:

Another option would be for “runtime objects” to have objects from the object graph injected statically (makes sense, since the injected instances are often singletons and not specific to any runtime object instance) and let the runtime object constructors only have “object identity” parameters.

This idiom/strategy arguably also clarifies the distinction between framework/service variables (static) and identity/business variables (instance) in an object.

Then again, having written both “static” and “singleton” in one post, I have probably already disqualified myself from having an opinion. ;-)

/Pär Eklund  

Balint Persics replied on Thu, 2012/08/23 - 10:27am in response to: Cedric Beust

I clearly do not see any real need for assisted injections. If an object is a real collaborator to another object, it must be the part of its identity (because it is part of its state). If it is not a part of its state, then the object should not be initialized with it. There is no point to have a direct, instantiation-time dependency on an object that is not a part of the state.

I think it is better to have the caller supply the dependency upon calling the needed methods, in the above example: when calling  isNear, the caller should supply the GeoService which to use, it is not the callee's responsibility.

Pär Eklund replied on Thu, 2012/08/23 - 11:09am

Isn't it so that at some point someone - either the caller or the callee - needs to get hold of this - or another - service-like object, which is mainly there to provide a service, not to contribute state?

In most of the frameworks I have used, ultimately these "service objects" are fetched from a central, singleton context object whenever needed - a context object which is often proprietary to the framework. IMHO, it is better to have this injected, e.g. statically as I suggested above, rather than to actively fetch it as ProperitaryFrameworkContext.getFooService().

Apart from rather depending on a (not necessarily) proprietary annotation than a call to a proprietary context object, having the service statically injected ensures that the service will be available everywhere in the object, even at construction time, without any effort on the programmer's part.

 

 

Thomas Wheeler replied on Thu, 2012/08/23 - 11:14am in response to: Balint Persics

I think this more closely captures what I was trying to say. Whether Address is termed a POJO/DTO or domain object or EJB, its relationship to the GeoService is as a transient collaborator if we have Address.isNear(). I would still probably be hesitant to have this method, but I could live with it if Address is a domain object.

Thomas

Balint Persics replied on Thu, 2012/08/23 - 11:28am in response to: Pär Eklund

A constructor has one and only one job: initialize an instance of the class to a correct initial state. If a service object does not contribute to the state of the object, it should not be injected to a constructor.

Instead it should be injected to the calls where this service object is used as a collaborator and this problem is already solved by method injections.

DI it is not that different from having objects from a context singleton, the real difference is that the instances are  not pulled from context by the injectee but pushed from context by the injector. In fact DI modules are these context objects. But collaborator lookup is replaced with injection. That's all.

 

 

Balint Persics replied on Thu, 2012/08/23 - 11:32am in response to: Thomas Wheeler

I think the idea of "being near" belongs to the domain of Addresses (locations, places, coordinates, whatever name suits you better) and not Persons. Persons may be near each other if their Addresses are near each other. That relation (an address is near some other address) belongs to the Address class and no one else. That's why (proper) domain modeling is good for: identify the real relations between domain objects and identify their methods.

Jose Maria Arranz replied on Fri, 2012/08/24 - 2:34am

I must recognize I'm not a fan of current state of "automatic" IoC practices (I do tons of "manually" IoC everyday), my main concern is VIRALITY of current IoC approaches, this virality makes IoC benefits near to none, virality means you have manage a non-singleton class like Person just to get injected a simple-stupid singleton object like GeoService, your Person object needs a convoluted factory process just to get injected our simple-stupid singleton. Because most of people are not very comfortable with these factories and because automatic IoC is easy to apply to singleton, this virality invites to convert any object into a singleton, the result is terrible because classes just coded to hold singletons are not different to classes with all methods static.

In summary most of the time "automatic" IoC invites you to "singleton programming" a sort of C-like programming far away of good OOP approaches.

Yes, I know that factories for non-singleton make things better but I think is not worth, this is why in Android development "automatic" IoC is almost missing in "mainstream" and has almost nothing to offer, because Android is NOT singleton programming.

Doing Android development, sometimes I take a look to Android's Java source code usually because some things are not very well documented and sometimes just curiosity.

Cedric, try to ask to your Android colleagues at Google about introducing some Guice into Android source code, I would answer something like: "in Android state management is by far much more complicated than singleton programming the kind of problem automatic IoC tries to easily simplify and this kind of stuff introduces more noise, rigidity and lost of performance than benefits just to reduce a bit the amount of code"

To be positive: we need built-in bytecode weaving into our tools, bytecode weaving is the only approach able to make healthy use of automatic IoC (in this case really automatic) without all of noise and virality of conventional semi-automatic IoC.

Just an Android example, I want something like:

class MyClassWithAnyKindOfLifeCycle
{
@Inject
Application app;
}


without the burden of trying to manually manage the lifecycle of MyClassWithAnyKindOfLifeCycle objects into the IoC framework just to get injected a simple Application object, I prefer manual IoC.

I think we are far away of this.

Finally: ignore complaints about bad OOP practices, I agree with you, is just an example to show IoC instead of showing how much you're good doing OOP (and I'm know you're very good).

Basil Abbas replied on Tue, 2013/11/26 - 12:08am

I think this post applied a cool feature of guice on a weak example of a Person domain class.

It would have been better shown the wisdom of the assisted inject if the class was an action class (command pattern) such as a validator action that will need to be instantiated later on.

A note by Dhanji Prasanna sums it up from his Dependency Injection book:

Most of the time, it’s quite easy to determine what objects ought to be created and managed by dependency injection. A rule of thumb is to leave anything that’s a service or action component to the purview of the injector and any class that models data to traditional, by-hand usage.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.