Springboot – 2. Inversion of Control (IoC) and Dependency Injection (DI)

?1. Inversion of Control (IoC) and Dependency Injection (DI):

  • Inversion of Control (IoC): The spring-core module implements the IoC container, which is one of the core concepts of the Spring framework. IoC refers to leaving the creation of objects and management of dependencies to the container, rather than hardcoding them in the code. Spring’s IoC container is responsible for managing the bean life cycle, dependencies, and bean assembly.

  • Dependency Injection (DI): The spring-core module injects dependencies between Beans into the container through dependency injection, thereby decoupling the tight coupling between components. Using DI, developers can inject dependencies into beans through constructors, setter methods, or field injection.

1. Define Bean:

In Spring Boot, you can use @Component and its derived annotations (such as @Service, @Repository, @Controller code>, etc.) to define Bean. These annotations mark a class as a bean in the Spring container. For example:

@Service
public class MyService {<!-- -->
    // ...
}

2. Dependency injection:

Spring Boot implements dependency injection through the IOC container. You can use the @Autowired annotation to inject dependent objects where they are needed. For example, use the @Autowired annotation on constructors, setter methods, and fields:

@Service
public class MyService {<!-- -->
    private final AnotherService anotherService;

    @Autowired
    public MyService(AnotherService anotherService) {<!-- -->
        this.anotherService = anotherService;
    }

    // ...
}

3. Configuration injection:

Spring Boot recommends using Java configuration classes to configure beans instead of XML configuration files. You can mark the configuration class with the @Configuration annotation, and use the @Bean annotation on the method to define the Bean. For example:

  • In Spring Boot, configuration classes are a way to configure applications using Java code, replacing traditional XML configuration files. Configuration classes are marked with the @Configuration annotation, and the @Bean annotation is usually used to define beans, as well as other configuration-related annotations to implement various functions. The following is a detailed explanation and examples of configuration classes in Spring Boot:

    • Create a configuration class: First, you need to create a Java class and mark it with the @Configuration annotation to make it a configuration class.

      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class AppConfig {<!-- -->
          // Configuration related methods will be defined here
      }
      
    • Define Bean: In the configuration class, you can use the @Bean annotation to define the Bean. The method’s return value type will become the bean’s type, and the method name will become the bean’s name.

      import org.springframework.context.annotation.Bean;
      
      @Configuration
      public class AppConfig {<!-- -->
          @Bean
          public MyService myService() {<!-- -->
              return new MyService();
          }
      }
      
    • Dependency Injection into Other Beans: In the configuration class, you can use the @Autowired annotation to dependency inject other beans, just like in other ordinary component classes.

      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class AppConfig {<!-- -->
          @Autowired
          private AnotherService anotherService;
      
          @Bean
          public MyService myService() {<!-- -->
              return new MyService(anotherService);
          }
      }
      
    • Use external properties: Configuration classes can also use the @Value annotation to inject external property values, or use the @ConfigurationProperties annotation to bind property configurations.

      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.stereotype.Component;
      
      @Component
      @ConfigurationProperties(prefix = "app.config")
      public class AppConfigProperties {<!-- -->
          private String greetingMessage;
          private int maxRetry;
          private int timeout;
      
          // Getter and setter methods for the properties
      }
      
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.ConfigurableApplicationContext;
      
      @SpringBootApplication
      public class Application {<!-- -->
      
          @Autowired
          private AppConfigProperties appConfig;
      
          public static void main(String[] args) {<!-- -->
              ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
              Application application = context.getBean(Application.class);
              application.printConfig();
          }
      
          public void printConfig() {<!-- -->
              System.out.println("Greeting message: " + appConfig.getGreetingMessage());
              System.out.println("Max retry: " + appConfig.getMaxRetry());
              System.out.println("Timeout: " + appConfig.getTimeout());
          }
      }
      
      
    • Combined configuration classes: Multiple configuration classes can be combined together to form a comprehensive configuration. Spring Boot will automatically merge these configuration classes into an application context.

      @Configuration
      public class DatabaseConfig {<!-- -->
          // Define database-related beans and configurations
      }
      
      @Configuration
      public class MessagingConfig {<!-- -->
          // Define messaging-related beans and configurations
      }
      
      @Configuration
      @Import({<!-- -->DatabaseConfig.class, MessagingConfig.class})
      public class AppConfig {<!-- -->
          // Main configuration and other beans
      }
      
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      
      public class MainApp {<!-- -->
          public static void main(String[] args) {<!-- -->
              AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
              // Get and use beans from the application context
          }
      }
      
    • Summary: Combining configuration classes is a method of combining multiple configuration classes to make the configuration clearer, modular and maintainable. You can effectively manage configuration in your application by creating a main configuration class and using the @Import annotation to import other configuration classes. This approach helps reduce configuration complexity and improves application scalability and maintainability.

4. Attribute injection:

In addition to constructor and setter method injection, you can also use the @Autowired annotation to perform dependency injection on properties. This way, Spring Boot will automatically inject dependencies into properties.

@Service
public class MyService {<!-- -->
    @Autowired
    private AnotherService anotherService;

    // ...
}

5. Qualifiers and Primary:

If there are multiple beans that implement the same interface, you can use the @Qualifier annotation to specify the specific bean to be injected. Alternatively, you can use the @Primary annotation to mark a primary Bean, which will be preferred for injection.

  • When you use dependency injection in Spring Boot, you may encounter multiple beans of the same type, for example, multiple classes that implement the same interface. In this case, to tell the Spring container which specific bean should be injected, you can use the @Qualifier annotation and the @Primary annotation.

    • @Qualifier annotation:
      The @Qualifier annotation is used to explicitly specify the name of the bean to be injected when there are multiple beans of the same type. By specifying the value of @Qualifier, you can tell the Spring container which specific bean should be injected.

      • Example:
      @Service
      public class MyService {<!-- -->
          private final AnotherService primaryAnotherService;
          private final AnotherService secondaryAnotherService;
      
          @Autowired
          public MyService(@Qualifier("primary") AnotherService primaryAnotherService,
                           @Qualifier("secondary") AnotherService secondaryAnotherService) {<!-- -->
              this.primaryAnotherService = primaryAnotherService;
              this.secondaryAnotherService = secondaryAnotherService;
          }
      
          // ...
      }
      
    • @Primary annotation:
      The @Primary annotation is used to mark a Bean as the preferred bean. When there are multiple beans of the same type, the Bean marked as @Primary will be injected first. If @Qualifier is not specified, then the @Primary annotated Bean will be the default selection.

      • Example:
      @Service
      @Primary
      public class PrimaryAnotherService implements AnotherService {<!-- -->
          // ...
      }
      
      @Service
      public class SecondaryAnotherService implements AnotherService {<!-- -->
          // ...
      }
      
      @Service
      public class MyService {<!-- -->
          private final AnotherService anotherService;
      
          @Autowired
          public MyService(AnotherService anotherService) {<!-- -->
              this.anotherService = anotherService;
          }
      
          // ...
      }
      
    • Summary: In Spring Boot, when there are multiple beans of the same type, you can use the @Qualifier annotation to specify the specific bean to be injected. In addition, you can also use the @Primary annotation to mark a bean as the preferred bean and inject it first. These annotations help choose between multiple candidate beans for your dependency injection needs.

6. Optional dependencies:

If the dependency object may not exist, you can use @Autowired(required = false) to mark the injected dependency as optional. If no matching bean is found, the injected field will be null.

@Service
public class MyService {<!-- -->
    @Autowired(required = false)
    private AnotherService optionalAnotherService;

    // ...
}

7. Attribute value injection:

Spring Boot also supports injecting property values into beans. You can use the @Value annotation to achieve this. The property values configured in the configuration file will be injected into the Bean’s fields or method parameters.

Property value injection is a mechanism for injecting external property values into beans in Spring Boot. This way, you can separate configuration information from your code, making your application more flexible and configurable. Spring Boot implements property value injection through the @Value annotation.

  • The following is a detailed explanation and example of property value injection in Spring Boot:

    • Defining property values in the configuration file: First, define the property values in the application.properties or application.yml configuration file. For example, in the application.properties file:

      app.greeting.message=Hello from properties file!
      
    • Use the @Value annotation in the Bean: Then, you can use the @Value annotation to inject property values in the Bean’s fields, constructor parameters, method parameters, etc. The value of the annotation is the name of the property read from the configuration file.

      • Example:
      @Service
      public class MyService {<!-- -->
          @Value("${app.greeting.message}")
          private String greetingMessage;
      
          public void printGreeting() {<!-- -->
              System.out.println(greetingMessage);
          }
      }
      
    • Using SpEL expressions: The @Value annotation also supports using Spring Expression Language (SpEL) to calculate property values. You can use ${expression} in annotations to reference SpEL expressions.

      • Example:
      @Service
      public class MyService {<!-- -->
          @Value("#{T(java.lang.Math).random()}")
          private double randomNumber;
      
          public void printRandomNumber() {<!-- -->
              System.out.println(randomNumber);
          }
      }
      
    • Default value: If the property does not exist in the configuration file, you can use the defaultValue attribute of the @Value annotation to set the default value.

      • Example:
      @Service
      public class MyService {<!-- -->
          @Value("${app.nonexistent.property:Default Value}")
          private String defaultValue;
      
          public void printDefaultValue() {<!-- -->
              System.out.println(defaultValue);
          }
      }
      
    • Summary: Property value injection is an important feature in Spring Boot. By using the @Value annotation, you can inject external property values into the Bean, making your application more flexible and reliable. Configurability. You can read property values from a configuration file and calculate them using SpEL expressions. Additionally, you can set a default value in case the property does not exist in the configuration file.

8. Collection injection:

If you need to inject multiple dependencies of the same type, you can use collections such as List, Set, and Map for collection injection.

  • In Spring Boot, collection injection is a mechanism to inject multiple dependent objects of the same type into a collection. This mechanism is ideal for scenarios where you need to deal with multiple dependent objects that implement the same interface or parent class. Spring Boot makes collection injection easy by using the @Autowired annotation combined with collection types such as List, Set, and Map. Very convenient.

    • The following is a detailed explanation and example of collection injection in Spring Boot:

    • Define multiple implementation classes: First, suppose you have multiple classes that implement the same interface, for example:

      public interface Plugin {<!-- -->
          void execute();
      }
      
      @Service
      public class PluginA implements Plugin {<!-- -->
          // ...
      }
      
      @Service
      public class PluginB implements Plugin {<!-- -->
          // ...
      }
      
    • Use collection injection in beans: Then, you can use the @Autowired annotation to combine the collection type (such as List< Plugin>, Set, Map, etc.) for collection injection.

      • Example:
      @Service
      public class PluginManager {<!-- -->
          private final List<Plugin> plugins;
      
          @Autowired
          public PluginManager(List<Plugin> plugins) {<!-- -->
              this.plugins = plugins;
          }
      
          public void executeAllPlugins() {<!-- -->
              for (Plugin plugin : plugins) {<!-- -->
                  plugin.execute();
              }
          }
      }
      
    • In the above example, the PluginManager class injects a List collection through the constructor, which contains all implementations of the Plugin interface Bean. Collection injection is a powerful feature in Spring Boot. It allows you to inject multiple dependency objects of the same type into a collection, allowing you to easily handle multiple dependencies that implement the same interface or parent class. By using the @Autowired annotation and collection types, you can easily implement collection injection in your beans to better manage and handle dependencies.

  • Summary: Dependency injection is an important design pattern in Spring Boot applications, by using @Autowired, @Qualifier, @Value Annotations like this allow you to decouple dependencies from your code, making it more maintainable and scalable. Combined with Spring Boot’s automatic configuration function, dependency injection can be implemented more conveniently, allowing you to focus more on the development of business logic.