Monday, March 7, 2016

@Autowired and optional dependencies

@Autowired annotation makes our lives easier. It also can result in decreased amount of code if we are using it on properties of the class. We need neither constructors nor setter methods. It looks great at first glance, but the benefits rarely come without cost.

Today I want to make you aware of the costs that have to be paid.

@Autowired(required=false)

@Autowired annotation by default has an element required set to true, which means that the annotated dependency is required. However, we can turn off the default behavior and make the dependency optional as follows:
@Autowired(required=false)
private Dependency dependency;

It can be useful and since not all dependencies are always required, introducing this possibility was reasonable.

So what’s the problem with dependencies annotated in this way?
Let’s look at the code:
class SomeClass {
   @Autowired private DependencyA dependencyA;
   @Autowired private DependencyB dependencyB;

   @Autowired(required=false)
   private DependencyC dependencyC;

   @Autowired(required=false)
   private DependencyD dependencyD;
}

We can create an instance of the SomeClass with following dependencies (all combinations are allowed):
  • DependencyA, DependencyB
  • DependencyA, DependencyB, DependencyC
  • DependencyA, DependencyB, DependencyD
  • DependencyA, DependencyB, DependencyC, DependencyD

And this is all great, but are you sure that all of those combinations are really useable and correct? What if the author of the class thought only about 1 and 4?

Optional dependencies - do it right!

If we are considering an example presented in the previous paragraph, there are two possible answers for the asked question:
  • All combinations are possible.
  • Only subset of the combinations is possible.

In case when all combinations are possible, I would leave the code as it is. If there is nothing that can go wrong and each state of the object is correct, then our code is descriptive enough. It clearly allows for anything so we can assume that anything that we will do will result in the creation of an object that we may work with.

What about the second point - subset of the combinations?
Let’s assume that only creating an object in state from the point 1 and 4 are valid ones. Leaving the code as it is can result in the wrong usage of the object. We are allowing for the creation an object in invalid states (points 2 and 3). What we can do about this?
In that case I think that we should drop @Autowired annotation. For the sake of the code readability and design quality it would be better to use constructors instead:
class SomeClass {
   private DependencyA dependencyA;
   private DependencyB dependencyB;
   private DependencyC dependencyC;
   private DependencyD dependencyD;

   public SomeClass(DependencyA dependencyA, DependencyB dependencyB) {
      this.dependencyA = dependencyA;
      this.dependencyB = dependencyB;
   }

   public SomeClass(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC, DependencyD dependencyD) {
      this(dependencyA, dependencyB);
      this.dependencyC = dependencyC;
      this.dependencyD = dependencyD;
   }
}

With this code you know everything what is needed. You know what dependencies are required to create a correct object.

Stay aware!

The article is not intended to convince you that it is better to not use @Autowired(required=false). Its purpose is to make you aware of the cost that you have to pay.

You have to protect your design, you have to protect object invariants and you should not allow for existence of the objects in invalid state. Of course, you may add a documentation or comment, but if we already have a semantic provided by the language which allows us to make it without additional effort we should use it.


No comments:

Post a Comment