DeepakBhalla

Spring Annotation based IoC

By Deepak Bhalla Tue 26 April 2016 6 min read

An Introduction to the move from XML based IoC to Annotation based IoC in Spring.

Introduction

Annotation based IoC (Inversion of Control) seems to work like magic and declarative .xml configurations which at one time seemed ubiquitous have all but dissapeared. You can instead use @Component, @Service, @Repository, and @Controller and never have to mess with manually declaring an XML configuration. So how do XML configurations and annotation based configurations work, what changed, and why?

XML Based IoC

Lets look at an .xml based configuration. It is nice to understand how you manually bootstrap an application with xml based IoC. You might see answers to common Spring related questions use this method. In this case we will have a simple Account interface, AccountImpl (its implementation) and a AccountHandler where the Account class is injected. I will show how these are manually configured in Spring.

Account.java

package com.deepakrb;

public interface Account {
    public String speakToMe();
}

AccountImpl.java

package com.deepakrb;

public class AccountImpl implements Account {
    private String accountName = "First"

    @Override
    public String speakToMe() {
        return "Account: " + this.accountName;
    }
}

This declares a simple Account Class which implements the Account interface with a single method speakToMe().

AccountHandler.java

package com.deepakrb;

import com.deepakrb.Account;

public class AccountHandler {
    private Account account;

    public void AccountHandler(Account account) {
        this.account = account;
    }

    @Override
    public String toString() {
        return "Account Handler [" + account.speakToMe() + "]";
    }
}

This declares a simple handler and uses the AccountHandler constructor to populate the private account property. Injected the account of type Account in the constructor is done as follows:

SpringConfiguration.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="AccountBean" class="com.deepakrb.AccountImpl"/>

    <bean id="AccountHandlerBean" AccountBean="com.deepakrb.AccountHandler">
        <constructor-arg ref="AccountBean"/>
    </bean>
</beans>

This uses the AccountHandlerBean bean (which is com.deepakrb.AccountHandler) with the constructor argument bean AccountBean. AccountHandlerBean is injected with the bean AccountBean (which is com.deepakrb.AccountImpl). Notice our AccountHandler class knows nothing about the AccountImpl class it will be injected with and only knows it will accept something with an Account interface. To bootstrap the project we use the following main class:

Main.java

package com.deepakrb;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("SpringConfiguration.xml");
        AccountHandler accountHandler = (AccountHandler) ctx.getBean("accountHandler");
        System.out.println(accountHandler);          // "Account Handler[Account: First]"
    }
}

Here an ApplicationContext (an interface) is created from a ClassPathXmlApplicationContext which loads SpringConfiguration.xml from a path. ApplicationContext gives us access to an instance of a Spring Container (which holds all beans) among other things. The context provides the getBean() method which uses the SpringConfiguration.xml to load the appropriate bean. The bean is cast to an AccountHandler instance using (AccountHandler). As you can see SpringConfiguration.xml was used to invert control making sure AccountHandler placed no absolute depdendency on instantiating its own instance of Account.

Annnotation based IoC

The .xml configuration meant that the workflow of the application was entirely dependent on a separate file. To understand what classes were injected you would consult another file which showed how they were linked. Constantly consulting an .xml can get very annoying. Annotations can be used as a more verbose way of declaring a class as a bean.

A more annotation based interpretation of the above case:

Account.java

package com.deepakrb;

public interface Account {
    public String speakToMe();
}

AccountImpl.java

package com.deepakrb;

@Component
public class AccountImpl implements Account
{
    private String accountName = "First"

    @Override
    public String speakToMe() {
        return "Account: " + this.accountName;
    }
}

The annotation @Component and its aliases @Service, @Repository, @Controller are simple declarations for beans which are picked up by Springs Class Path Scanner. Class Path Scanning is Spring's way of detecting beans without using a declarative XML configuration.

Notice there was no need to declare the interface as a component. There are a few reasons for this: - It is good to keep interfaces as general as possible. - If AccountHandler expects an Account interface as its constructor argument and therefore places an absolute depdendency on that class (called an injection point type).

AccountHandler.java

package com.deepakrb;

import com.deepakrb.Account;

@Component
public class AccountHandler {
    private Account account;

    @Autowired
    public void AccountHandler(Account account) {
        this.account = account;
    }

    @Override
    public String toString() {
        return "Account Handler [" + account.speakToMe() + "]";
    }
}

@Autowired is used to automatically inject the correct constructor argument.

Config.java

package com.deepakrb

@Configuration
@ComponentScan("com.deepakrb") // search the com.company package for @Component classes
public class Config {...}

Main.java

package com.deepakrb

import com.deepakrb.Config

public class Main {
    public static void main(String[] args) {
        JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(Config);
        AccountHandler accountHandler = (AccountHandler) ctx.getBean(AccountHandler);
        accountHandler.doStuff();
    }
}

It is config and the @ComponentScan annotation that enables component scanning.

So what happened to beans and when do I use the @bean annotation?

It should be noted that a @Configuration class where you can use Java notation to manually declare beans which will not be picked up by class path scanning manually.

Config.java

pacakge com.deepakrb

@Configuration
public class Config {
    @Bean
    public Account accountBean() {
        return new Account();
    }
}

Injection Point

@Autowired
private Account account;

Multiple Implementations

As a final note it is worth talking about multiple implementations. As long as there is only one implementation of an interface with Spring's component scan enabled spring will automatically inject the correct implementation. But what if you need multiple implementations? that is after all the point of an interface. There are many options for dealing with this, the most common is @Qualifier.

AccountImpl1.java

@Component(value = "1")
public class AccountImpl1 implements Account {...}

AccountImpl2.java

@Component(value = "2")
public class AccountImpl2 implements Account {...}

AccountHandler.java

@Component
public class AccountHandler() {
    private Account account;

    @Autowired
    @Qualifier("1") // Implements AccountImpl1
    public AccountHandler(Account account) {...}
}
Deepak Bhalla