Skip to content

Components and IoC

One of the most important and interesting functionalities of Spring is the IoC container, which is responsible for the dependency injection (DI) mechanism. Thanks to this, we do not have to create objects manually (e.g. using the new operator). In order to understand how DI works, you need to understand:

  • what are beans
  • how to register beans in an IoC container (Inversion of Control)
  • how to inject registered beans

Beany and registration in the IoC container

A bean (or a so-called component) is an object that is managed by an IoC container. In turn, the IoC container is the mechanism that is responsible for managing the beans. This means that the developer is not directly responsible for creating or deleting such an object.

There are several ways to register an object in an IoC container. We can use for this:

  • appropriate configuration in an xml file (rarely used in newer applications)
  • use one of the annotations above the class (the so-called stereotypes)
    • @Component
    • @Service
    • @Repository
    • @Controller
  • use the class marked with the annotation @Configuration with methods marked with the annotation@Bean

NOTE: The collection of all beans in the application is often colloquially called context.

NOTE: The context is resolved (ie creating all beans) at the start of the application.

Stereotypes

The above-mentioned annotations used over the classes, i.e. @Component, @Service, @Repository and @Controller, perform the same function - they give control over the class to the IoC container. There are some differences between them:

  • @Component - a generic component, if you are not sure which annotation to use - this one will be the correct choice
  • @Service - functionally does not differ in any way from the@Component annotation. The class is only information based and represents the service layer, i.e. one that contains business logic.
  • @Repository - represents the database access layer (i.e. the class that uses e.g. theEntityManager instance to query the relational database). The only difference from the @Component annotation is that in the event of an error on the database layer, we can get more detailed information in the exceptions.
  • @Controller - works just like @Component but the bean that uses this annotation also goes to the so-called WebApplicationContext i.e. to the part of the context that represents the objects that make up the application's web layer.

NOTE: It is always possible to use the @Component annotation instead of the@Repository or @Service annotation.

The following example shows how to register a class in an IoC container:

import org.springframework.stereotype.Component;

@Component
public class SimpleCalculator {

  public Double add(final Double valA, final Double valB) {
    return valA +valB;
  }
}
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service // you can use@Component instead of @Service
public class TextProcessorService {

  public String processSentences(final List<String> sentences) {
    return sentences.stream()
        .filter(sentence -> !sentence.isBlank())
        .map(sentence -> sentence.substring(0, 1).toUpperCase() + sentence.substring(1, sentence.length() - 1) + ".")
        .collect(Collectors.joining(" "));
  }
}

CommandLineRunner

Spring is a framework, so it has places where you can call upon your code. Most often, we can do this by creating a component of a specific type (ie a class that implements a certain interface or extends a certain class). One such interface is the CommandLineRunner. It has a run method that is run at application startup, e.g .:

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class HelloWorldLogger implements CommandLineRunner {
  @Override
  public void run(final String... args) throws Exception {
    log.info("Hello from one of our first spring components");
  }
}

Having only the above definition of the class in the application (so we never create an instance of it directly in the application), and then restarting the application, we should see in the logs:

INFO 13224 --- [main] pl.sdacademy.sb.HelloWorldLogger : Hello from one of our first spring compoments

@Configuration and @Bean

Stereotypes allow to define a class as a bean. Sometimes, for certain functionalities, we need to define several related beans (using other existing objects). In this case, you can put their definitions in a single class. Such a class should be marked with the annotation @Configuration. The definition of such a class should include methods that return object instances that go to the "dependency bag" (i.e. to the context). Each of these methods must be additionally marked with the annotation @Bean.

Objects created in methods marked with the annotation @Bean are created "manually"(ie using the new operator or, for example, using creational patterns.

The following example shows a class definition that defines two beans:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.Converter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SimpleWebConfiguration {

  @Bean
  public ObjectMapper objectMapper() {
    final ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    return objectMapper;
  }

  @Bean
  public Converter<Object, Object> simpleObjectConverter() {
    return new Converter<Object, Object>() {
      @Override
      public Object convert(final Object o) {
        // method implementation
        return null;
      }

      @Override
      public JavaType getInputType(final TypeFactory typeFactory) {
        // method implementation
        return null;
      }

      @Override
      public JavaType getOutputType(final TypeFactory typeFactory) {
        // method implementation
        return null;
      }
    };
  }
}

NOTE: A class marked with the annotation @Configuration is also a component.

Injecting dependencies

No dependency injection

Suppose we have defined classes ClassA, ClassB, ClassC, andClassD:

public class ClassA { /* class definition */ }
public class ClassB { /* class definition */ }
public class ClassC { /* class definition */ }
public class ClassD { /* class definition */ }

All of these classes are used by the class ClassE:

public class ClassE {
  private final ClassA classA;
  private final ClassB classB;
  private final ClassC classC;
  private final ClassD classD;

  public ClassE(final ClassA classA,
                final ClassB classB,
                final ClassC classC,
                final ClassD classD) {
    this.classA = classA;
    this.classB = classB;
    this.classC = classC;
    this.classD = classD;
  } 
}

If we want to create an instance of the classE class without using the injection mechanism, we need to create all dependent classes manually, and then pass them as arguments to the constructor:

public static void main(String[] args) {
  final ClassA classA = new ClassA();
  final ClassB classB = new ClassB();
  final ClassC classC = new ClassC();
  final ClassD classD = new ClassD();

  final ClassE classE = new ClassE(classA, classB, classC, classD);
}

The approach outlined above is very cumbersome and flawed. A better way is to drop the responsibility for creating dependency objects on a certain dependency injection mechanism.

Dependency injection methods

In order to maintain the principles of [SOLID] (../ design_patterns_and_best_practices/good-practices/SOLID.md) and good programming practices, we try to divide the functionality into smaller, separate classes. At some point during development, however, the developer wants to use already created and registered beans. For this purpose, we can use the @Autowired annotation.

Spring offers three ways to inject dependencies:

  • by a constructor
  • by using a setter
  • directly to the class field

Injection with a constructor

Constructor injection is the preferred way to use the DI mechanism that Spring offers. However, we must remember that:

  • we can only inject beans (i.e. classes in the context of the application)
  • we can only inject beans into other beans

The types of dependencies that we want to inject are given as arguments of the target bean's constructor, e.g .:

import org.springframework.stereotype.Component;

@Component
public class Dependency {
}
import org.springframework.stereotype.Component;

@Component
public class ClassWithDependency {

  private final Dependency dependency;

  // inject via constructor, dependency (bean) is an argument to the constructor
  public ClassWithDependency(final Dependency dependency) {
    this.dependency = dependency;
  }
}

NOTE:_ Class dependencies are often defined as fields with the final modifier.

NOTE: On the Internet you will find a lot of examples that additionally have an annotation @Autowired over the constructor. In case of injection via constructor not needed.

Injecting with a setter

Injection with the setter is the less used DI mechanism in Spring. To inject a dependency with a setter it must be additionally marked with the annotation @Autowired:

import org.springframework.stereotype.Component;

@Component
public class Dependency {
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ClassWithDependency {

  private Dependency dependency;

  @Autowired
  public void setDependency(final Dependency dependency) {
    this.dependency = dependency;
  }
}

Injecting into the field

A third way to inject dependencies is to directly inject them into the class fields. It requires an @Autowired annotation over the class field. This method is often used in tests that use Spring mechanisms.

The following example shows this injection method:

import org.springframework.stereotype.Component;

@Component
public class DependencyA {
}
import org.springframework.stereotype.Component;

@Component
public class DependencyB {
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ClassWithDependencies {

  @Autowired
  private DependencyA dependencyA;

  @Autowired
  private DependencyB dependencyB;
}