Master Spring Beans: Essential Tips and Best Practices
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
- 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.
- 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.
- 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);
}
}
- Application Startup:
- When
MySpringBootApplication
starts, Spring Boot initializes the IoC container.
2. Bean Creation:
- Spring scans the application for beans and finds
MyService
andMyRepository
. - Spring creates an instance of
MyRepository
.
3. Dependency Injection:
- Spring creates an instance of
MyService
and injects theMyRepository
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 onMyRepository
, Spring ensures thatMyRepository
bean is created first beforeMyService
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
- Bean Instantiation
- Dependency Injection
- Lifecycle Management
- 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 theAppConfig
class, which defines the beans and their configurations using annotations.MyService
is retrieved from the context usingcontext.getBean(MyService.class)
, whereMyService
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:
- 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. - 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.
- 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. - 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. - 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.