Inversion of Control in Spring Framework

Bellamer - Sep 15 - - Dev Community

Inversion of Control (IoC) and Dependency Injection (DI) are two fundamental concepts in the Spring Framework. Traditionally, objects are responsible for creating and managing their own dependencies. However, IoC flips this responsibility by handing over the control of object creation and dependency management to a framework like Spring.

This shift offers several advantages:

  • Easier Implementation Swapping: Different implementations can be swapped with minimal changes to the codebase.
  • Increased Modularity: Application components become more modular, facilitating separation of concerns.
  • Enhanced Testability: Components can be tested in isolation, which simplifies mocking and other testing strategies.

IoC can be implemented through various mechanisms, including design patterns like the Factory Pattern, Strategy Pattern, or Service Locator Pattern. However, the most common and powerful way to achieve IoC is through Dependency Injection.

Understanding Dependency Injection

Dependency Injection (DI) is a technique where the framework injects dependencies into an object rather than the object creating the dependencies itself. There are different types of DI in Spring:

  • Constructor Injection: Dependencies are provided through the constructor of the class.
  • Setter Injection: Dependencies are injected through setter methods.
  • Field Injection: Dependencies are directly assigned to fields using annotations.

For example, in a simple service class, you might see something like this:

@Service
public class OrderService {

    private final PaymentService paymentService;

    @Autowired
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void processOrder(Order order) {
        paymentService.processPayment(order.getPaymentDetails());
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, PaymentService is injected into OrderService via constructor injection, which is generally preferred for its clear dependencies and ease of testing.

IoC Container: BeanFactory vs. ApplicationContext

Spring provides an IoC container that is responsible for managing the lifecycle of beans (objects managed by Spring). The basic interface for this container is BeanFactory. However, most applications use ApplicationContext, which extends BeanFactory and offers additional features.

BeanFactory

  • Basic IoC container: It provides basic functionalities to create and manage beans.
  • Lazy Initialization: Beans are created when they are first requested.

ApplicationContext

  • Advanced IoC container: In addition to the basic features of BeanFactory, it provides:
    • Internationalization (i18n) support
    • Event propagation and handling
    • Asynchronous request handling
    • Integration with Aspect-Oriented Programming (AOP)
  • Eager Initialization: Beans can be instantiated when the application starts, making them available for immediate use.

Example:

// Getting a bean from the ApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = context.getBean(MyBean.class);
Enter fullscreen mode Exit fullscreen mode

In this example, ApplicationContext is used to retrieve a bean. If you were using a basic BeanFactory, it would offer similar functionality but without the additional benefits of ApplicationContext.

Accessing the ApplicationContext

There are two common ways to access the current ApplicationContext:

  1. Autowired: Inject the ApplicationContext directly into your component.

    @Autowired
    private ApplicationContext applicationContext;
    
  2. ApplicationContextAware: Implement the ApplicationContextAware interface, which allows you to retrieve the ApplicationContext as needed.

    public class MyBean implements ApplicationContextAware {
        private ApplicationContext applicationContext;
    
        @Override
        public void setApplicationContext(ApplicationContext context) throws BeansException {
            this.applicationContext = context;
        }
    }
    

Lifecycle of a Spring Application

When a Spring application starts, a series of steps take place:

  1. Processing Environment Variables and Properties: The Environment object is created, incorporating settings from application.properties or application.yml.

  2. Creating the ApplicationContext: The appropriate ApplicationContext type is determined and instantiated. For instance, a Spring Boot application may use AnnotationConfigServletWebServerApplicationContext for Java-based configurations.

  3. Loading Bean Definitions: Spring loads bean definitions from multiple sources, such as annotated classes, @Configuration classes, or XML files. Each bean definition includes information about the bean's type, scope, dependencies, and lifecycle callbacks.

  4. Processing BeanFactoryPostProcessors: These processors modify the bean definitions before the actual beans are created.

  5. Dependency Resolution and Bean Creation: The ApplicationContext resolves dependencies and creates the beans. If a bean has dependencies on other beans, those dependencies are created first.

Bean Scopes in Spring

Spring supports various bean scopes, which define the lifecycle and visibility of a bean within the container:

  • Singleton (Default): A single instance of the bean is created for the entire application context.
  • Prototype: A new instance is created every time the bean is requested.
  • Request: A new bean instance is created for each HTTP request (web applications).
  • Session: A new bean instance is created for each HTTP session (web applications).

Example:

@Bean
@Scope("prototype")
public MyPrototypeBean myPrototypeBean() {
    return new MyPrototypeBean();
}
Enter fullscreen mode Exit fullscreen mode

In this example, a new MyPrototypeBean instance is created each time it is requested from the container.

Proxy Objects in Bean Scopes

Consider a scenario where a Singleton-scoped bean depends on a Prototype-scoped bean. Normally, the prototype bean would only be created once during the singleton's initialization. To ensure that a new instance of the prototype bean is provided each time, Spring uses proxy objects.

Example with Proxies:

@Component
@Scope(value = "singleton")
public class SingletonBean {

    @Autowired
    @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
    private PrototypeBean prototypeBean;

    public void showMessage() {
        System.out.println(prototypeBean.getMessage());
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, ScopedProxyMode.TARGET_CLASS ensures that a new PrototypeBean instance is returned each time getMessage() is called.

Difference Between Spring Framework and Spring Boot

Spring Boot is an extension of the Spring Framework that simplifies the setup and development of new Spring applications. It provides:

  • Embedded Application Servers: Easily run Spring applications as standalone apps with embedded Tomcat, Jetty, or Undertow.
  • Auto-Configuration: Automatically configures beans based on the classpath and other settings.
  • Simplified Packaging: Package applications as executable JARs or WARs.
  • Integrated Monitoring and Health Checks: Built-in endpoints to monitor the health and status of the application.

Auto-Configuration in Spring Boot

Auto-Configuration is a powerful feature in Spring Boot that configures many things for you based on the dependencies in your classpath. This is controlled by conditional annotations such as:

  • @ConditionalOnClass
  • @ConditionalOnMissingClass
  • @ConditionalOnBean
  • @ConditionalOnMissingBean

Example:

For instance, if you have Jackson on your classpath, Spring Boot will automatically configure an ObjectMapper for JSON serialization:

@Configuration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
    // Configuration details...
}
Enter fullscreen mode Exit fullscreen mode

If ObjectMapper is present, this configuration is automatically applied. Otherwise, it is skipped.

Viewing Active Auto-Configurations

You can log the active auto-configurations during startup by adding the following to your application.properties:

logging.level.org.springframework.boot.autoconfigure=DEBUG
Enter fullscreen mode Exit fullscreen mode

This will generate a detailed report showing which configurations were applied and which were not, helping you troubleshoot or understand the application's configuration.

Conclusion

Inversion of Control and Dependency Injection are core concepts that enable the flexibility, modularity, and testability that make Spring so powerful. By understanding how Spring's IoC container works, how beans are managed, and how Spring Boot extends these capabilities, you can develop robust, maintainable applications more efficiently.

. . . . . . . . . . . . . . . . . . . . . . . . . .