Configuration classes in Java replace the old XML configuration approach. This makes setting up Spring applications more readable and convenient.
A configuration class is a Java class used to describe your application's configuration. In Spring these classes are annotated with @Configuration. That tells the IoC-container that the class contains methods that create beans. Those methods are annotated with @Bean.
You can think of a configuration class like the menu at your favorite cafe. With @Bean you describe what will be served (the objects), and the IoC-container acts like the waiter that brings everything you need (dependency injection) to your table.
Example of a simple configuration class
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public String welcomeMessage() {
return "Welcome to the Spring Framework!";
}
}
In our example welcomeMessage is a bean that returns a string. The configuration class AppConfig contains a method annotated with @Bean, and Spring knows that this method creates a bean managed by the IoC-container.
But don't worry — we won't stop at a bean that just says "hi". We'll ramp things up and create more complex configurations.
Creating beans and working with dependencies
Configuration classes are useful when creating objects that have dependencies. For example, if we have a Car class that depends on an Engine, we can wire that up in a configuration class.
Example with dependencies
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("Car is driving with " + engine.getType() + " engine.");
}
}
Now let's create a configuration class for these objects:
@Configuration
public class VehicleConfig {
@Bean
public Engine engine() {
return new Engine("V8");
}
@Bean
public Car car() {
return new Car(engine());
}
}
In the car method we call the engine method to pass it into the Car constructor. This lets Spring know that the Car bean depends on the Engine bean. Now the IoC-container will take care of wiring our object correctly.
How does this work?
- Spring creates an
Enginebean with typeV8. - Then Spring creates the
Carbean, and theEnginebean is automatically passed into its constructor.
This handy process reduces the amount of code we'd have to write manually. Honestly, nobody enjoys wiring up all dependencies by hand.
Declaring dependencies between beans explicitly
If for some reason you want to explicitly tell the Car bean that it needs the Engine bean, you can use the @Qualifier annotation (spoiler: you'll need it in later lectures).
Complex dependencies: beans depending on multiple other beans
Say we're building a flight booking service. We need to combine several dependencies, like a client for working with a database and a payment service. You can wire all of that in a configuration class.
public class PaymentService {
public void pay() {
System.out.println("Payment processed successfully!");
}
}
public class BookingService {
private PaymentService paymentService;
public BookingService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void bookFlight() {
System.out.println("Booking completed.");
paymentService.pay();
}
}
Let's configure this with a configuration class:
@Configuration
public class BookingConfig {
@Bean
public PaymentService paymentService() {
return new PaymentService();
}
@Bean
public BookingService bookingService() {
return new BookingService(paymentService());
}
}
Now we get:
- Encapsulation of object-creation logic.
- Less boilerplate in the main application logic.
- Flexibility: configuration changes are easy to adapt to.
Avoiding bean duplication problems
One common mistake is accidentally creating duplicate beans. If you try to declare two beans with the same name, Spring will throw an error: "Found multiple candidates for dependency injection". To avoid this, make sure your @Bean methods have unique names and are correctly defined.
Practice: setting up a real project
Let's take a step toward our example app. We'll create a service to manage books in a library using a configuration class.
Business logic
public class Book {
private String title;
private String author;
public Book(String title, String author) {
this.title = title;
this.author = author;
}
@Override
public String toString() {
return "Book: " + title + " by " + author;
}
}
public class LibraryService {
private Book book;
public LibraryService(Book book) {
this.book = book;
}
public void displayBookInfo() {
System.out.println(book.toString());
}
}
Configuration class
@Configuration
public class LibraryConfig {
@Bean
public Book defaultBook() {
return new Book("Clean Code", "Robert C. Martin");
}
@Bean
public LibraryService libraryService() {
return new LibraryService(defaultBook());
}
}
Using it in the main application
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfig.class);
LibraryService libraryService = context.getBean(LibraryService.class);
libraryService.displayBookInfo();
}
}
Program output:
Book: Clean Code by Robert C. Martin
Congrats! We've covered how to use configuration classes and create Spring Beans. Now you can construct objects, wire their dependencies, and make the IoC-container work for you.
GO TO FULL VERSION