Skip to main content

Spring Beans

Introduction

In the Spring Framework ecosystem, beans are the foundation of your application. But what exactly is a Spring bean? In simple terms, a Spring bean is an object that is created, managed, and configured by the Spring IoC (Inversion of Control) container.

Spring beans are not just regular Java objects - they are special because:

  • They are instantiated, assembled, and managed by the Spring container
  • They have their lifecycle managed by Spring
  • They can be configured and wired together through dependency injection

In this guide, we'll explore Spring beans in depth, understanding their creation, configuration, and how they work together in a Spring application.

What is a Spring Bean?

A Spring bean is simply a Java object that is instantiated, assembled, and managed by Spring's IoC container. Any regular Java POJO (Plain Old Java Object) can be a Spring bean if it's configured to be recognized by the Spring container.

Key Characteristics of Spring Beans

  1. Created by the Spring container: Spring creates instances of beans defined in your configuration.
  2. Managed lifecycle: Spring manages when beans are created and destroyed.
  3. Dependency management: Spring handles relationships between beans through dependency injection.
  4. Configuration metadata: Bean information is provided to Spring through XML, annotations, or Java code.

How to Define a Spring Bean

There are several ways to define a Spring bean:

1. XML-based Configuration

XML was the traditional way of defining beans in Spring:

xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="messageService" class="com.example.MessageServiceImpl">
<!-- property injections can be defined here -->
<property name="message" value="Hello, Spring!" />
</bean>
</beans>

2. Annotation-based Configuration

Modern Spring applications often use annotations to define beans:

java
@Component
public class MessageServiceImpl implements MessageService {

private String message;

@Value("Hello, Spring!")
public void setMessage(String message) {
this.message = message;
}

public String getMessage() {
return message;
}
}

You would need to enable component scanning in your configuration:

java
@Configuration
@ComponentScan("com.example")
public class AppConfig {
// Configuration code
}

3. Java-based Configuration

You can also define beans programmatically in Java configuration classes:

java
@Configuration
public class AppConfig {

@Bean
public MessageService messageService() {
MessageServiceImpl service = new MessageServiceImpl();
service.setMessage("Hello, Spring!");
return service;
}
}

Spring Bean Lifecycle

A Spring bean goes through a well-defined lifecycle:

  1. Instantiation: The Spring container creates an instance of the bean.
  2. Populating Properties: The container sets the values and references to the bean's properties.
  3. Aware Interfaces: If the bean implements specific interfaces (like BeanNameAware), the Spring container calls their methods.
  4. BeanPostProcessor: Pre-initialization callbacks are invoked.
  5. InitializingBean/init-method: Initialization callbacks are executed.
  6. BeanPostProcessor: Post-initialization callbacks are invoked.
  7. Bean Ready to Use: Now the bean is ready for use by the application.
  8. DisposableBean/destroy-method: When the container is shut down, destruction callbacks are invoked.

You can hook into this lifecycle using various methods:

java
@Component
public class LifecycleDemoBean implements InitializingBean, DisposableBean {

@PostConstruct
public void postConstruct() {
System.out.println("PostConstruct method called");
}

@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean's afterPropertiesSet method called");
}

@PreDestroy
public void preDestroy() {
System.out.println("PreDestroy method called");
}

@Override
public void destroy() throws Exception {
System.out.println("DisposableBean's destroy method called");
}
}

Bean Scopes

Spring beans can be defined with different scopes:

  1. singleton (default): One instance per Spring IoC container
  2. prototype: New instance each time the bean is requested
  3. request: One instance per HTTP request (web-aware Spring context)
  4. session: One instance per HTTP session (web-aware Spring context)
  5. application: One instance per ServletContext (web-aware Spring context)
  6. websocket: One instance per WebSocket (web-aware Spring context)

Example of setting bean scope with annotations:

java
@Component
@Scope("prototype")
public class PrototypeScopedBean {
// Bean definition
}

Example with XML:

xml
<bean id="prototypeBean" class="com.example.PrototypeBean" scope="prototype" />

Practical Example: Building a Simple Todo Application

Let's see Spring beans in action by creating a simple Todo application.

First, we'll define our model:

java
public class TodoItem {
private Long id;
private String task;
private boolean completed;

// Constructors, getters, and setters
// ...
}

Next, let's create a repository interface:

java
public interface TodoRepository {
List<TodoItem> findAll();
TodoItem findById(Long id);
void save(TodoItem todoItem);
void delete(Long id);
}

Here's an in-memory implementation:

java
@Repository
public class InMemoryTodoRepository implements TodoRepository {

private final Map<Long, TodoItem> todoMap = new HashMap<>();
private Long nextId = 1L;

@Override
public List<TodoItem> findAll() {
return new ArrayList<>(todoMap.values());
}

@Override
public TodoItem findById(Long id) {
return todoMap.get(id);
}

@Override
public void save(TodoItem todoItem) {
if (todoItem.getId() == null) {
todoItem.setId(nextId++);
}
todoMap.put(todoItem.getId(), todoItem);
}

@Override
public void delete(Long id) {
todoMap.remove(id);
}
}

Now, let's create a service layer:

java
public interface TodoService {
List<TodoItem> getAllTodos();
TodoItem getTodoById(Long id);
void createTodo(String task);
void markAsCompleted(Long id);
void deleteTodo(Long id);
}

And its implementation:

java
@Service
public class TodoServiceImpl implements TodoService {

private final TodoRepository todoRepository;

@Autowired
public TodoServiceImpl(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}

@Override
public List<TodoItem> getAllTodos() {
return todoRepository.findAll();
}

@Override
public TodoItem getTodoById(Long id) {
return todoRepository.findById(id);
}

@Override
public void createTodo(String task) {
TodoItem todoItem = new TodoItem();
todoItem.setTask(task);
todoItem.setCompleted(false);
todoRepository.save(todoItem);
}

@Override
public void markAsCompleted(Long id) {
TodoItem todoItem = todoRepository.findById(id);
if (todoItem != null) {
todoItem.setCompleted(true);
todoRepository.save(todoItem);
}
}

@Override
public void deleteTodo(Long id) {
todoRepository.delete(id);
}
}

Finally, let's create a configuration class:

java
@Configuration
@ComponentScan("com.example.todo")
public class TodoAppConfig {
// Any additional bean definitions can go here
}

And a simple application to test it:

java
public class TodoApplication {

public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(TodoAppConfig.class);

TodoService todoService = context.getBean(TodoService.class);

// Create some todo items
todoService.createTodo("Learn Spring Framework");
todoService.createTodo("Build a REST API");
todoService.createTodo("Write unit tests");

// Mark one as completed
todoService.markAsCompleted(2L);

// Display all todos
System.out.println("All Todo Items:");
List<TodoItem> todos = todoService.getAllTodos();
for (TodoItem item : todos) {
System.out.println(item.getId() + ": " + item.getTask() +
(item.isCompleted() ? " (Completed)" : " (Pending)"));
}

// Clean up
((AnnotationConfigApplicationContext)context).close();
}
}

Output:

All Todo Items:
1: Learn Spring Framework (Pending)
2: Build a REST API (Completed)
3: Write unit tests (Pending)

In this example:

  • TodoItem is a simple POJO
  • InMemoryTodoRepository is a Spring bean annotated with @Repository
  • TodoServiceImpl is a Spring bean annotated with @Service
  • TodoAppConfig is a configuration class that enables component scanning
  • Dependency injection happens when TodoRepository is injected into TodoServiceImpl

Best Practices for Working with Beans

  1. Favor constructor injection: It ensures required dependencies are provided and helps with immutability.

    java
    @Service
    public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;

    @Autowired
    public UserService(UserRepository userRepository, EmailService emailService) {
    this.userRepository = userRepository;
    this.emailService = emailService;
    }
    // Methods...
    }
  2. Use appropriate bean scopes: Most beans should be singletons unless there's a specific reason to use another scope.

  3. Avoid circular dependencies: They make your code harder to test and maintain.

  4. Keep beans focused: Follow the Single Responsibility Principle.

  5. Use meaningful bean names: When you have multiple beans of the same type.

    java
    @Configuration
    public class DatabaseConfig {

    @Bean("primaryDataSource")
    public DataSource primaryDataSource() {
    // configuration
    }

    @Bean("auditingDataSource")
    public DataSource auditingDataSource() {
    // configuration
    }
    }

Summary

Spring beans are the building blocks of a Spring application. They're ordinary Java objects that are instantiated, assembled, and managed by the Spring IoC container. Key points to remember:

  • Spring beans can be configured via XML, annotations, or Java configuration
  • Beans have a well-defined lifecycle managed by the Spring container
  • Different bean scopes determine how and when bean instances are created
  • Dependency injection helps wire beans together
  • Spring's automatic bean detection through component scanning simplifies configuration

By understanding Spring beans, you have grasped one of the core concepts of the Spring Framework. This knowledge forms the foundation for building robust, modular, and maintainable applications using Spring.

Additional Resources

  1. Spring Framework Documentation
  2. Spring Bean Scopes
  3. Spring Bean Lifecycle

Exercises

  1. Create a Spring application with at least three beans that depend on each other.
  2. Implement a custom bean with lifecycle callbacks.
  3. Create beans with different scopes and observe their behavior.
  4. Convert an existing XML-based bean configuration to Java-based configuration.
  5. Implement the Todo application from this tutorial and add additional features like updating todo items.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)