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:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
.
- 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”.
- 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!