Master Spring Beans: Essential Tips and Best Practices

Ashish Singh
5 min readJul 14, 2024

--

What is a Spring Bean?

A Spring bean is an object that is instantiated, assembled, and managed by the Spring IoC container. Beans are the backbone of a Spring application, defined in the Spring configuration files or annotated with specific Spring annotations.

Whenever you use stereotype annotations like @Component, @Service, @Repository, and @Controller, you are telling Spring to create an instance of that class (just like using new MyClassName()) and manage it as a Spring bean. These beans are then stored in the Spring IoC (Inversion of Control) container, which handles their lifecycle and dependencies.

For example:

  • @Component: Generic stereotype for any Spring-managed component.
  • @Service: Specialization of @Component for service-layer classes.
  • @Repository: Specialization of @Component for data access objects (DAOs).
  • @Controller: Specialization of @Component for web controllers in Spring MVC.

By using these annotations, you are effectively creating and managing Java objects (beans) within the Spring framework, which the IoC container takes care of for you.

To know Ways to create Spring beans(Java object) : link

When do bean creation and dependency injection occur in Spring? Runtime or compile-time?

In Spring, bean creation and dependency injection occur at runtime. Here’s a more detailed explanation:

Bean Creation and Dependency Injection in Spring

  1. Application Startup:
  • When you start your Spring application (e.g., by running the main method in a Spring Boot application), Spring initializes the IoC (Inversion of Control) container.
  • Spring scans the classpath for classes annotated with @Component, @Service, @Repository, @Controller, and other relevant annotations.
  1. Bean Creation:
  • For each class annotated with these stereotype annotations, Spring creates an instance of the class (a bean).
  • This process is known as bean instantiation and occurs at runtime during the startup of the application context.
  1. Dependency Injection:
  • After creating the beans, Spring performs dependency injection. This means Spring injects the required dependencies into the beans.
  • Dependencies can be injected through constructors, setters, or fields, typically marked with @Autowired.

Example Workflow

Let’s illustrate this with an example:

@Service
public class MyService {
private final MyRepository myRepository;

@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
}

@Repository
public class MyRepository {
// Repository implementation
}

@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
}
  1. Application Startup:
  • When MySpringBootApplication starts, Spring Boot initializes the IoC container.

2. Bean Creation:

  • Spring scans the application for beans and finds MyService and MyRepository.
  • Spring creates an instance of MyRepository.

3. Dependency Injection:

  • Spring creates an instance of MyService and injects the MyRepository instance into it via the constructor.

Runtime Process

All these steps happen at runtime:

  • Runtime Bean Creation: Spring creates bean instances when the application context is initialized.
  • Runtime Dependency Injection: Spring resolves dependencies and injects them into the appropriate beans at runtime.

When a bean is being created by the Spring IoC container, its dependencies are resolved and injected first. This means that before a bean can be fully constructed, all the beans it depends on must already be created and available in the Spring context.

In the example provided, where MyService depends on MyRepository, Spring ensures that MyRepository bean is created first before MyService bean is instantiated

Understanding the Spring Application Context

The Spring Application Context is a fundamental part of the Spring Framework, serving as the container that manages and wires together beans (i.e., objects) in a Spring application. It plays a crucial role in the Inversion of Control (IoC) and Dependency Injection (DI) design principles, enabling flexible and loosely coupled application development.

Key Responsibilities

  1. Bean Instantiation
  2. Dependency Injection
  3. Lifecycle Management
  4. Configuration Management

Example of Using Application Context

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

// Retrieve beans from the context
MyService myService = context.getBean(MyService.class);
myService.doSomething();
}
}

In this example:

  • AnnotationConfigApplicationContext is used to load the application context based on the AppConfig class, which defines the beans and their configurations using annotations.
  • MyService is retrieved from the context using context.getBean(MyService.class), where MyService is a Spring bean managed by the application context.

What Happens When You Create an Object with new Keyword in Spring?

When you instantiate a class using the new keyword within another class in a Spring application, you're bypassing Spring's dependency injection mechanism. Here's a breakdown of what occurs:

  1. Direct Object Creation: Using new directly invokes the class constructor, creating an object outside of Spring's managed environment. This object isn't controlled or managed by Spring's IoC container.
  2. Lack of Spring Bean Lifecycle Management: Objects created this way don’t undergo Spring’s lifecycle phases such as instantiation, initialization, dependency injection, and destruction. They operate independently of Spring’s lifecycle management.
  3. Dependency Injection Issues: Since Spring isn’t managing the object, dependencies annotated with @Autowired, @Inject, or @Resource aren't automatically injected. Dependencies must be manually initialized through setters or constructors.
  4. Absence of AOP Features: Spring’s powerful Aspect-Oriented Programming (AOP) features, including method interception, transaction management, and logging, aren’t applied to objects instantiated with new. These aspects are exclusive to Spring-managed beans.
  5. Potential for Errors: Creating objects with new within Spring-managed components can lead to errors and inconsistencies, especially when relying on Spring-managed resources or services that are expected to be injected.

Example:

Consider this example to illustrate the implications

@Service
class XYZ {
@Autowired
private UserRepo userRepo;

public List<User> getUsers() {
return userRepo.findAll();
}
}

@RestController
class ABC {
@GetMapping("/api/v1/users")
public List<User> getUsers() {
XYZ xyz = new XYZ(); // Object created using `new` keyword
return xyz.getUsers(); // NullPointerException: userRepo is null
}
}

Why Does This Fail?

In the provided code, NullPointerException occurs because XYZ is instantiated using new within ABC, bypassing Spring's dependency injection. As a result, userRepo isn't injected, leading to a NullPointerException when accessing methods.

Correct Approach:

To resolve this issue and leverage Spring’s dependency injection properly, refactor the code to use constructor injection.

@Service
class XYZ {
private final UserRepo userRepo;

@Autowired
public XYZ(UserRepo userRepo) {
this.userRepo = userRepo;
}

public List<User> getUsers() {
return userRepo.findAll();
}
}

@RestController
class ABC {
private final XYZ xyz;

@Autowired
public ABC(XYZ xyz) {
this.xyz = xyz;
}

@GetMapping("/api/v1/users")
public List<User> getUsers() {
return xyz.getUsers();
}
}

Here, XYZ is injected into ABC via constructor injection, ensuring proper dependency management by Spring.

--

--

No responses yet