Wiring Beans in Spring

What is Wiring Beans?

When you start building applications with the Spring Framework, one important concept you’ll encounter is wiring beans. But what exactly does that mean? Simply put, wiring beans is all about how different objects in your application connect and work together.

Understanding Spring Beans

In Spring, a bean is just an object that the framework manages. These beans live in something called the Spring context, which is like a special storage area in your application’s memory. The Spring context keeps track of all the beans you create and ensures they can communicate with each other effectively.

Establishing Relationships Between Beans

By wiring beans, you are establishing relationships among them, enabling one bean to utilize another’s functionality. For example, if you have a Car bean, it might need to work with an Engine bean. Wiring them together allows the Car to use the Engine to run smoothly. Below is the code for Car, Engine, and DemoApplication. The VehicleConfig class is left out for now, as it will be used later to demonstrate how wiring beans is done in Spring.

public class Engine {
    private String type;

    public Engine(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }
}
public class Car {
    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void drive() {
        System.out.println("Driving with engine: " + engine.getType());
    }
}
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        // Run the Spring Boot application and get the application context
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);

        // Retrieve the Car bean from the context
        Car car = context.getBean(Car.class);
       
        // Use the Car bean
        car.drive(); // Output: Driving with engine: V8
    }
}

Three Main Approaches to Wiring Beans


There are three main approaches to establish a relationship between two beans in Spring:

  1. Bean Method Reference:

In Spring, you can refer to an existing bean in the context by calling a method annotated with @Bean from another @Bean method. Spring is smart enough to recognize that you’re referencing a bean it already manages. So, if the bean has already been created, Spring will not call the method again to create a new instance. Instead, it will return the existing bean.

@Configuration
public class VehicleConfig {

    @Bean
    public Engine engine() {
        return new Engine("V8");
    }

    @Bean
    public Car car() {
        // Referring to the engine bean by calling the method
        return new Car(engine());
    }
}

In this code, the car() method calls engine(). Spring knows that the engine() method is linked to a bean, so instead of creating a new Engine object each time, it checks if an Engine bean is already in the context. If the bean has been created earlier, Spring returns that instance. If not, it creates the Engine bean, stores it in the context, and then returns it to the car() method.

This approach only works with a method annotated with @Bean inside a @Configuration class, allowing you to reference an existing bean by calling another @Bean method. Spring ensures that this call returns the same instance from the context, preventing duplicate creations. For beans defined using @Component, @Service, or similar annotations, you’ll need to use parameter injection or the @Autowired annotation to manage dependencies instead of calling the method directly.

  1. Bean Parameter Injection:

Another approach is to wire the beans by defining the bean to be injected as a parameter in the @Bean method. This method works regardless of how the bean was created, whether with @Bean or with a stereotype annotation like @Component.

@Configuration
public class VehicleConfig {

    @Bean
    public Engine engine() {
        return new Engine("V8");
    }

    @Bean
    public Car car(Engine engine) {
        // Spring automatically injects the engine bean into this method
        return new Car(engine);
    }
}

In this example, Spring automatically injects the Engine bean into the car() method by defining it as a method parameter. Spring looks up the Engine bean in its context and passes it to the car() method without the need for explicitly calling the engine() method. What we see here is called dependency injection, unlike the bean method reference above. In this process, we allow the framework to assign a value to a specific field or parameter instead of explicitly creating an instance of the Engine.

  1. Autowired Annotation:

    Just like bean parameter injection, @Autowired is another form of dependency injection, where Spring supplies the necessary values to fields or parameters from the application context. It is typically used in class definitions marked with common stereotype annotations like @Component, @Service, or @Repository. There are three ways to use @Autowired: field injection, constructor injection, and setter injection. Each has its use cases, but constructor injection is typically the preferred method in real-world projects.

Note: When using the @Autowired examples below, the VehicleConfig class is no longer needed, and you’ll need to modify both the Engine and Car classes. The type parameter for the Engine class needs to be hardcoded, as shown in the code snippet below. If you want to customize the Engine bean and use another type, like a V4 engine, you would then keep the old constructor and use a configuration class, as shown in the earlier @Bean examples.

@Component
public class Engine {
    private final String type = "V8"; // Hardcoded type

    public String getType() {
        return type;
    }
}

The @Component annotation tells Spring to create an instance of the class with the default no-argument constructor and add it to its context. To wire beans, we must first add the object instance to the Spring context.

  1. Field Injection
@Component
public class Car {
    @Autowired
    private Engine engine; // Field injection

    public void drive() {
        System.out.println("Driving with engine: " + engine.getType());
    }
}

With field injection, you can’t make the Engine field final because Spring first calls the no-argument constructor to create the Car object and then injects the dependency afterward. To mark a field as final, it must be initialized at the time of creation, which is why constructor injection is typically recommended. 

  1. Constructor Injection
@Component
public class Car {
    private final Engine engine; // Dependency is final

    @Autowired
    public Car(Engine engine) { // Constructor injection
        this.engine = engine;
    }

    public void drive() {
        System.out.println("Driving with engine: " + engine.getType());
    }
}

Constructor injection allows you to declare the dependency as final, ensuring it can only be assigned once during object creation. This means the field can only be set once, making your code more stable and less prone to bugs. With Spring version 4.3 and later, if your class has only one constructor, you can skip adding the @Autowired annotation. So in the code above, removing @Autowired would still compile and run just fine.

  1. Setter Injection
@Component
public class Car {
    private Engine engine;

    @Autowired
    public void setEngine(Engine engine) { // Setter injection
        this.engine = engine;
    }

    public void drive() {
        System.out.println("Driving with engine: " + engine.getType());
    }
}

Setter injection isn’t commonly used because it can make code harder to read and doesn’t allow you to mark fields as final. However, you might see it in some older apps, so it’s good to be aware of it.

Circular Dependency in Spring

Circular dependency happens when two or more beans depend on each other, creating a loop that can confuse Spring during runtime. For example, if Bean A needs Bean B, and Bean B also needs Bean A, Spring can’t figure out which one to create first.

@Component
public class BeanA {
    private final BeanB beanB;

    @Autowired
    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
}

@Component
public class BeanB {
    private final BeanA beanA;

    @Autowired
    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}

In this example, BeanA and BeanB cannot be created because they depend on each other.

Dealing with Multiple Beans in the Spring Context

Sometimes, you might have multiple beans of the same type in the Spring context, and Spring won’t know which one to use. In these cases, Spring will try to match the bean based on the parameter name, but if it can’t, you can guide Spring by using annotations like @Primary or @Qualifier to select the correct bean. Refer to the topmost implementation of the Car and Engine class, which includes a type parameter, at the beginning of this post.

  1. Matching Based on Parameter 

If the name of a method parameter matches the name of a bean defined in the Spring context, Spring will automatically inject that bean.

@Configuration
public class VehicleConfig {

    @Bean
    public Engine v8Engine() {
        return new Engine("V8");
    }

    @Bean
    public Engine v6Engine() {
        return new Engine("V6");
    }

    @Bean
    public Car car(Engine v6Engine) {
        // Spring automatically injects the v6Engine bean
        return new Car(v6Engine);
    }
}

In this example, the car() method parameter is named v6Engine, which matches the name of the v6Engine bean in the context. Therefore, Spring will inject the v6Engine bean into the Car constructor. Running this code will log out “Driving with engine: V6”. If you want to inject the v8Engine, simply change the parameter name from v6Engine to v8Engine

  1. Using @Primary

When you have multiple beans of the same type, you can mark one as @Primary to tell Spring to use it as the default if no other bean is explicitly specified. 

@Configuration
public class VehicleConfig {

    @Bean
    @Primary
    public Engine v8Engine() {
        return new Engine("V8");
    }

    @Bean
    public Engine v6Engine() {
        return new Engine("V6");
    }

    @Bean
    public Car car(Engine engine) {
        // Spring injects the primary bean (V8 engine)
        return new Car(engine);
    }
}

In this case, Spring will automatically inject the v8Engine into the Car bean because it is marked as @Primary. Running this code will log out “Driving with engine: V8”. 

  1. Using @Qualifier 

If you want to specify which bean to inject when there are multiple options, you can use @Qualifier

@Configuration
public class VehicleConfig {

    @Bean
    public Engine v8Engine() {
        return new Engine("V8");
    }

    @Bean
    public Engine v6Engine() {
        return new Engine("V6");
    }

    @Bean
    public Car car(@Qualifier("v6Engine") Engine engine) {
        // Spring injects the V6 engine based on the qualifier
        return new Car(engine);
    }
}

In this example, @Qualifier("v6Engine") tells Spring to inject the v6Engine bean instead of the v8Engine. This is especially useful when you have multiple beans of the same type, allowing you to clearly specify which one should be used.

By default, the @Qualifier uses the bean name, which is the same as the method name. You can also give a bean a different name using @Bean(name = "customName") and then reference it with @Qualifier("customName") when injecting.

@Configuration
public class VehicleConfig {

    @Bean(name = "customV8Engine")
    public Engine v8Engine() {
        return new Engine("V8");
    }

    @Bean(name = "customV6Engine")
    public Engine v6Engine() {
        return new Engine("V6");
    }

    @Bean
    public Car car(@Qualifier("customV8Engine") Engine engine) {
        // Spring injects the custom V8 engine
        return new Car(engine);
    }
}

In this example, we have two engine beans: customV8Engine and customV6Engine. The @Qualifier("customV8Engine") annotation tells Spring to use the customV8Engine bean for the Car bean.

Conclusion

Wiring beans in Spring helps different parts of your application work together smoothly. You can connect beans using method references, parameter injections, or the @Autowired annotation. If you have multiple beans, you can use the parameter name matching, the @Primary annotation for a default choice or @Qualifier to specify exactly which one to use. By understanding these concepts, you’ll find it easier to build applications with Spring!