Dependency Inversion Types in Sprint Boot

There are two basic types of Dependency Injection: 

constructor and setter. 

Spring recommends constructor based dependency in most cases. From the Spring docs: “Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.”

Constructor DI

Let’s say I have this POJO:

public class MeaningOfLife {


    final int lifeInt;

    final String lifeString;


    public MeaningOfLife(int lifeInt, String lifeString) {

        this.lifeInt = lifeInt;

        this.lifeString = lifeString;

    }


    public int getLifeInt() {

        return lifeInt;

    }


    public String getLifeString() {

        return lifeString;

    }

}

Here, we are using the @Value annotation to pick up properties from the environment and, importantly, we are providing defaults. This construct: @Value("#{ @environment['life.int'] ?: 0 }") int intLife can be read as:
Set the value of intLife to 0 unless Spring can load a property named life.int from application.properties or an environment variable named LIFE_INT

By virtue of it being a component, we can now autowire MeaningOfLife elsewhere in our app, such as our HomeController:

 ...
    @Autowired
    MeaningOfLife meaningOfLife;
...

    @RequestMapping("meaningOfLife")
    public String meaningOfLife() {
        return meaningOfLife.getLifeString();
    }
...


If you launch the app like so:
mvn clean install
LIFE_STRING=42 java -jar target/*.jar &

and then hit our meaning of life endpoint: http localhost:8080/meaningOfLife, you will see it returns

42

Setter DI

Let’s say I have this interface:

public interface Nameable {
 
    public String getName();
}

And I have a couple of implementation classes that I’ll expose as beans:

and

@Component
public class Person implements Nameable {
 
    @Override
    public String getName() {
        return "Micah";
    }
}

Let’s look at another component that will inject Person and Dog as setters:

@Component
public class NameHelper {
 
    private Nameable person;
    private Nameable dog;
 
    public String getName(String type) {
        switch (type) {
            case "person":
                return person.getName();
            case "dog":
                return dog.getName();
            default:
                return "UNKNOWN";
        }
    }
 
    @Autowired
    @Qualifier("person")
    public void setPerson(Nameable person) {
        this.person = person;
    }
 
    @Autowired
    @Qualifier("dog")
    public void setDog(Nameable dog) {
        this.dog = dog;
    }
}

Notice that both of our setters take a Nameable parameter. In order have Spring call these setters properly when it instantiates the NameHelper, we have to give it a hint to know which implementation to inject. In this case, we use the @Qualifier annotation with the bean name we want to inject. There’s a little bit of Spring magic going on here in that a bean’s default name will be it’s class name converted into standard Java variable format. So, it the class was named MyVeryImportantBean, it’s default name in Spring would be myVeryImportantBean.

In our HomeController, I’ve added a new method:


...
    @Autowired
    NameHelper nameHelper;
...
    @RequestMapping("/name")
    public String name(@RequestParam String type) {
        return nameHelper.getName(type);
    }
 
...

By default a @RequestParam is required, so in order to hit this endpoint, you must give it a type query parameter:

http localhost:8080/name?type=person

The above will respond with Micah.

Among the cool features of Spring is that you can autowire an array of bean interface type and Spring will load it up with all of the concrete objects its instantiated of that type. For instance, I’ve added the following to our NameHelper component:


...
    private Nameable[] nameables;
...
    public String[] getAllNames() {
        return Arrays.stream(nameables).map((Nameable::getName)).toArray(String[]::new);
    }
...
    @Autowired
    public void setNameables(Nameable[] nameables) {
        this.nameables = nameables;
    }
...

The @Autowired setter will be called by Spring with an array containing a Person and a Dog Nameable. If we added another class that implements Nameable, it would automatically be added to this list.

The getAllNames method uses some of the nice Java 8 streams interface to give us an array of the calls to the getName method for each of the Nameables in the array.

In our HomeController we can add an endpoint to exercise this


...
    @Autowired
    NameHelper nameHelper;
...
    @RequestMapping("/allNames")
    public String[] name() {
        return nameHelper.getAllNames();
    }
...

Hitting this endpoint, http localhost:8080/allNames returns:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Date: Wed, 27 Jul 2016 21:36:20 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
 
[
    "Fluffy",
    "Micah"
]

Bringing it Home

It’s easy to get a list of all of the beans that are loaded into the Spring application context. These beans can be referenced and injected into other components of your application.

I’ve added a /beans endpoint to our HomeController to drive this point home, so to speak

...
    @Autowired
    ApplicationContext appContext;
...
    @RequestMapping("/beans")
    public Map<String, String[]> beans(@RequestParam(required = false) String q) {
        Map<String, String[]> retMap = new HashMap<>();
 
        String[] retArray = Arrays.stream(appContext.getBeanDefinitionNames())
            .filter(beanName ->
                (q == null || q.length() == 0) ||
                beanName.toLowerCase().contains(q.trim().toLowerCase())
            )
            .toArray(String[]::new);
 
        retMap.put("beans", retArray);
        return retMap;
    }
...

In this case, it takes an options query parameter: q. If you don’t provide the parameter, it will return all the beans Spring has available.

Try: http localhost:8080/beans

It’s a long list. Let’s look for some of the beans we created in this example:

`http localhost:8080/beans?q=greeting


HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Date: Wed, 27 Jul 2016 21:42:21 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
 
{
    "beans": [
        "greetingServiceConfig",
        "englishGreetingService"
    ]
}

Notice that there’s no frenchGreetingService. That’s because of our @ConditionalOnProperty setting in GreetingServiceConfig.

We’ve covered a lot of ground in this post. We looked at the history of Dependency Injection, its use in Spring services and components, as well as different approaches including constructor and setter dependency injection.

Comments

Popular posts from this blog

Microservice Pattern: SAGA

Microservice Pattern: Database per service Context

SQL vs NoSQL | Difference between SQL & NoSQL | SQL Vs NoSQL Tutorial | SQL, NoSQL system design