Avoid private field dependency injection – here is why

442 2
Avoid private field dependency injection - here is why

Dependency injection is a great way of decoupling system components, assuming they are written using a contract-implementation approach.

The dependency injection containers allow you to select the desired component implementation to be injected into a specific component, or they might just find one implementation for you in your classpath, or better, they can create one implementation on the fly, as Spring does (Spring-Data).

In Java, DI containers do more than beans lookup. They also manage beans lifecycle and do other stuff I’m not going to be talking about. I will focus on how we use the DI containers and not on what they offer.

Our study case:

@Dependent
public class Order{

    //...
 
    @Inject

    private PaymentsGateway payments;


    public void pay(){

       //do payment here

    }

  
    //...

}

I’m pretty sure you recognize that code. Pay attention on the payments field:

Encapsulation with zero effort

Being private, the payments field is well encapsulated, meaning no external component will have access to it, unless we expose it willingly through a getter method.

Immutability with zero effort

If the Order class does not offer a setter method to modify the payments field nor a constructor, then, we can assume the Order class is Immutable. Once the DI container sets a value there, nothing else can change it, of course, unless we put on some gloves and use reflection to dig (this is actually how the DI container changes the field’s value).

We already know how good is to use private field dependency injection. Now, lets face the reality: how bad is it? Wait! I’m not just going to pull up a CONS list and System.out.println it. Not really. I will do it in a more elegant way:

When I figured out private field dependency injection was evil

I was writing a CDI based project and I had to write a few unit tests on classes that were using private field dependency injection. I needed to start a CDI container within my tests. I was going to use DeltaSpike as usual, but something made me stop and ask myself “Why do I need to start a DI container to run some basic unit tests?”. The answer was “because I have no way to set a value to the private field”. All I wanted was to inject a mock component to the bean under tests. I couldn’t, because I had no way to set the mock component instance to the private field. I was coupled to the DI container because only the container could change that field. I could have used Reflection, but, my test code would fail after each refactoring session, because in reflection I would be referring to the field name as a string and that name could change while refactoring. On the other hand, using reflection is a hack. I was looking for a more natural and elegant solution.

The solution was already there, I just had to look closely:

When you want to be able to define the value of a private field, you have two options: The first one is to create a constructor that receives the value to be set and the second one is to create a setter for such private field.

The difference between these two is that creating a constructor allows you to preserve the immutability, while creating the setter doesn’t, meaning anything gets the chance to set a different value.

Anyway, I wouldn’t need to start a DI container within my tests, because I would be able to set the value myself. I would be decoupled from the DI container. This is the big win of Constructor and Setter based dependency injection.

I got rid of DeltaSpike simply by adopting Constructor and Setter based dependency injection and it actually felt more natural to set the mocks myself.

I’m not saying abandon private field injection as the article’s headline suggests, I’m saying: Be aware of what you are losing when using it. As long as you can justify it, you should use it, otherwise, you know what to do.

Both Spring and CDI support constructor and setter based dependency injection. Lets modify the “Order” class to use constructor and setter dependency injection:

CDI Setter dependency injection example

@Dependent
public class Order{

 
     private PaymentsGateway payments;


     @Inject

     public void setPayments(PaymentsGateway p){

 
        this.payments = p;

     }

}

CDI Constructor dependency injection example

@Dependent
public class Order{


    private PaymentsGateway payments;


    @Inject

    public Order(PaymentsGateway p){

        this.payments = p;

    }
 

}

Would you stick to private field dependency injection? Would you really abandon it? Share the “why” with us. Leave a comment.

It’s always a pleasure.

(Visited 1,170 times, 1 visits today)

Mário Júnior

Mário Francisco Júnior is the Head Of Software Development at Vodacom Mozambique.