Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › Spring › IoC container and DI

The Spring container and beans

You never wrote new, so who creates the object and passes it in?

For the order service to use a discount policy object, someone has to create that object. If the order service creates it directly with new, the choice of implementation is fixed in the code of the order service. In Spring, the container creates the objects (beans), picks the right one, and injects it wherever it is needed.

Checked against real execution · All 30 combinations were run on Spring Boot 4.1.1 and checked against the results

Concepts in this lab

Inversion of Control (IoC)
This is a design principle in which a framework (the container), not the developer's code, takes charge of creating objects and connecting them to each other. In an ordinary program, your code creates and calls the objects it needs, but here the container creates the objects, puts them where they are needed, and calls your code at the right time. The name comes from the fact that the party in control is reversed.
Spring bean
This is an object that the Spring IoC container creates and manages. Unlike an object that a developer creates directly with new, the container injects its dependencies and manages its initialization and destruction, and additional features such as transactions and AOP can be applied to it through a proxy.
@Configuration and @Bean
@Configuration marks a configuration class that contains bean definitions, and the object returned by each @Bean method inside it is registered as a bean. Use them when you need to make a bean from a class in an external library that you cannot modify, or when you need to control the creation process yourself. Classes that you write yourself are usually registered with @Component or a related annotation.
@Primary and @Qualifier
These are two annotations that determine which bean is injected when there are two or more beans of the same type. The Spring container looks up the bean to inject by type, and when several beans match and there is no clue to decide which one to use (@Primary, @Qualifier, or a name), it throws a NoUniqueBeanDefinitionException and the application does not start. @Primary goes on the side that registers the bean (a class or a @Bean method) and marks it as "the one to prefer when there are several". @Qualifier goes on the side that receives the injection (a constructor parameter or a field) and names the bean to use directly. When both are present, the bean specified with @Qualifier is injected.
NoSuchBeanDefinitionException and NoUniqueBeanDefinitionException
These are two exceptions that the container throws when it cannot determine which bean to inject. NoSuchBeanDefinitionException is thrown when no bean of the required type is registered at all, and NoUniqueBeanDefinitionException is thrown when there are two or more beans of the same type and the container cannot choose one. Singleton beans are created when the application starts, so these exceptions usually appear at startup, not while a request is being handled, if the bean is a singleton, and the application does not start. For the former, check whether a bean registration (@Component or @Bean) is missing, and for the latter, check whether another bean of the same type has been added.

Try these

Running with the default settings

  1. Container startup: The container scans the classes and registers those annotated with @Component as beans (component scanning). The registered beans are orderService, fixedDiscountPolicy, rateDiscountPolicy. The name of a bean is the class name with its first letter changed to lowercase.
  2. Creating the orderService bean: The constructor of OrderService requires an argument of type DiscountPolicy. The container looks among the registered beans for those that match this type. Number of candidates: 2 (fixedDiscountPolicy, rateDiscountPolicy).
  3. There are two candidates, and nothing tells the container which one to inject. The container does not pick one arbitrarily and throws a NoUniqueBeanDefinitionException. The application fails to start.

Result of this run

Code

interface DiscountPolicy {
    int discount(int price);
}

@Component
class FixedDiscountPolicy implements DiscountPolicy {
    public int discount(int price) { return 1000; }   // always 1000 won
}

@Component
class RateDiscountPolicy implements DiscountPolicy {
    public int discount(int price) { return price / 10; }   // 10%
}

@Component
class OrderService {
    private final DiscountPolicy discountPolicy;

    OrderService(DiscountPolicy discountPolicy) {   // the container passes it in
        this.discountPolicy = discountPolicy;
    }

    int order(int price) {
        return price - discountPolicy.discount(price);
    }
}

// Where it is used: retrieved from the container
OrderService orderService = context.getBean(OrderService.class);
int pay = orderService.order(20000);

Assumptions of this model

All 30 combinations were run on Spring Boot 4.1.1 (Spring Framework 7.0.9) on this PC and checked against the results. For each combination, source code with the same structure as on the screen was compiled. The cases that create the object directly with new were run as they are, and the cases that use the container were started with component scanning. Then these were compared: whether startup succeeded, the name of the exception, the implementation injected into OrderService, the discount amount, who called the constructor of the implementation object (confirmed from the call stack), and the number of implementation objects created. The "File to edit to change the implementation" column is what the checking program found by directly comparing which files differ between two sources in which only the implementation to use was changed. For a startup failure, the name shown is that of the innermost cause, and an UnsatisfiedDependencyException wraps it on the outside. Simplifications on the screen: the order in which the container creates the bean objects depends on the registration order, so the lab does not state it (the "Bean creation and injection" lab covers the order). It was also confirmed by execution that, even when both implementations are registered and nothing marks which to choose, startup succeeds if the name of the constructor parameter is the same as a bean name (for example, rateDiscountPolicy), and that bean is injected. The parameter name in this lab is discountPolicy, which is not the same as either bean name. Field injection, setter injection, and receiving all beans of the same type as a List are not covered.

Next lab: Bean creation and injection

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these