DEV Community

Cover image for What is Inversion of Control (IoC) in Spring
Abdulcelil Cercenazi
Abdulcelil Cercenazi

Posted on

What is Inversion of Control (IoC) in Spring

Meet Mr. Beans πŸ₯Έ

A Bean is a Java object. That's it.
Java objects are units that hold data and logic which make our application do its work. Objects can depend on other objects (dependencies) to complete a certain task.

In Java, creating dependencies is usually done in the constructor of the class using the new keyword.


Example of Traditional Bean Creation πŸ§‘β€πŸ”§

Say we have a DownloadService that depends on a ConnectionService.
The ConnectionService needs an address and a password to set up its connection.

public class ConnectionService {  

    public ConnectionService(String address, String password)  
    {  
        // use address and password to set up the connection  
  }  
    public void connect()  
    {  
        // connecting things  
  }  
}
Enter fullscreen mode Exit fullscreen mode

Now, if the DownloadService wanted to use the ConnectionService it has to know how to construct it. It has to get the password, the address and call the ConnectionService constructor.

public class DownloadService {  
    ConnectionService connectionService;  

    public DownloadService()  
    {  
        String password = getPassword();  
        String address = getAddress();  
        connectionService = new ConnectionService(address, password);  
    }  
    public void download()  
    {  
        connectionService.connect();  
        // download things  
  }  
    private String getAddress()  
    {  
        return "address";  
    }  

    private String getPassword()  
    {  
        return "pass";  
    }  
}
Enter fullscreen mode Exit fullscreen mode

This is all fine and dandy, but what if we had 10 or more classes that use the ConnnectionService? Then they all have to know how to get the password, the address and call the constructor.

This is certainly not maintainable and hard to follow around.

Inversion of Control (IoC) to the Rescue πŸ‹οΈβ€β™‚οΈ

In the software development world, there is a well-known rule called the Single Responsibility Principle. It urges developers to assign a single responsibility to a method or a class.
So in our above example, the DownloadService should not know how to construct the complicated ConnectionService, it should just use it.

What the IoC pattern suggests is that the DownloadService service should invert the responsibility of creating the ConnectionService to some other entity.

This entity could be, for example, a factory class.

public class ConnectionFactory {  

    public ConnectionService createConnectionService()  
    {  
        String password = getPassword();  
        String address = getAddress();  
        return new ConnectionService(address, password);  
    }  

    private String getAddress()  
    {  
        return "address";  
    }  

    private String getPassword()  
    {  
        return "pass";  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Then in the DownloadService, we call the ConnectionFactoy which knows how to construct a ConnectionService object.

public class DownloadService {  
    ConnectionService connectionService;  

    public DownloadService()  
    {  
        connectionService = new ConnectionFactory().createConnectionService();  
    }  
    public void download()  
    {  
        connectionService.connect();  
        // download things  
  }  
}
Enter fullscreen mode Exit fullscreen mode

IoC in Spring ☘️

Spring offers a helping hand using the IoC Spring Container. All we have to do is describe our beans to the container in a configuration class and it will give them to us whenever we want.

Let's test this with our above code:

public class ConnectionService {  

    public ConnectionService(String address, String password)  
    {  
        // use address and password to setup the connection  
  }  
    public void connect()  
    {  
        System.out.println("Connecting...");  
        // connecting things  
  }  
}
Enter fullscreen mode Exit fullscreen mode

Now, let's introduce the configuration class which will provide us with the ConnectionService bean.

@Configuration
public class AppConfiguration {  
  @Bean  
  ConnectionService connectionService()  
    {  
        String address = getAddress();  
        String password = getPassword();  
        return new ConnectionService(address, password);  
    }  

    private String getAddress()  
    {  
        return "address";  
    }  

    private String getPassword()  
    {  
        return "pass";  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Two new things to note here:

  • The @Configuration annotation indicates that the class has @Bean definition methods. So Spring container can process the class and generate Spring Beans to be used in the application.
  • The @Bean declares the object returned from the method as a Bean to be managed by Spring IoC container.

Finally, let's check out our DownloadService class which will have the ConnectionService Bean injected in.

@Component  
public class DownloadService {  
    private final ConnectionService connectionService;  

    @Autowired
    public DownloadService(ConnectionService connectionService)  
    {  
        this.connectionService = connectionService;  
    }  
    public void download()  
    {  
        connectionService.connect();  
        // download things  
  }  
}
Enter fullscreen mode Exit fullscreen mode

An important detail to pay attention to here is the @Component and @Autowired annotations.

The @Component will tell Spring to manage the DownloadService as a Bean. Doing this, Spring will create the class and inject any Beans it depends on (ConnectionService in this example) into the class.

The @Autowired will tell Spring to get the objects declared in the constructor parameters from the Spring IoC container, which in our case was declared in the AppConfiguration class.


Now, that was a good move to have the IoC principle implemented in our system. Spring, however, offers an even more convenient way to handle IoC.

We can remove the AppConfiguration class and simply have the logic of instantiating the ConnectionService inside of it and have Spring create the Bean and make it ready for use.

@Component  
public class ConnectionService {  

    public ConnectionService()  
    {  
        // use address and password to setup the connection  
        String address = getAddress();  
        String password = getPassword();  
    }  
    public void connect()  
    {  
        // connecting things  
    }  

    private String getAddress()  
    {  
        return "address";  
    }  

    private String getPassword()  
    {  
        return "pass";  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Note the @Component annotation which tells Spring to treat this class as a Bean and have it in the Spring IoC ready for the DownloadService to use.

Beans' Life Cycle πŸŒ• πŸŒ– πŸŒ— 🌘 πŸŒ‘

Spring Bean factory manages the lifecycle of beans created through the Spring container and controls the creation and destruction of the beans.

Let's explore the phases of a Spring Bean's life cycle:

  1. Bean definition.

    • It's defined by the @Bean annotation over methods in the configuration file (the one annotated with @Configuration file).
    • Or using the @Component annotation and its derivatives (@Service, @Controller, etc..) over classes.
  2. Bean Instantiation.

    • Spring instantiate bean objects and loads the objects into the ApplicationContext.
  3. Populating Bean properties.

    • Spring scans the beans and sets relevant properties as id, scope, and the default values.
  4. Pre-Initialization

  5. AfterPropertiesSet

    • Spring executes the afterPropertiesSet() methods of the beans which implement InitializingBean.
  6. Custom Initialization

    • Spring triggers any initialization methods that we define.
  7. Post-initialization

  8. Ready

    • The Bean is ready and injected into all other dependencies.
  9. Pre-Destroy

    • Spring triggers @PreDestroy annotated methods in this phase
  10. Destroy

    • Spring executes the destroy() methods of DisposableBean implementations
  11. Custom Destruction

    • Spring triggers any custom destruction methods that we've defined.

Conclusion ✍️

The principle of Inversion of Control helps us write clean, maintainable code.

This can be achieved by separating the logic of creating complex objects into a class of their own (separation of concerns).

Spring offers a neat solution using Spring's ApplicationContext and Spring's IoC container. The container treats our objects as Beans which have life cycles managed by Spring and custom methods that we can introduce.

Top comments (0)