Generally in simple terms dependency is nothing but a Has-A relationship in Java.
- In the provided code example, the PersonController class has a dependency on PersonService to perform its tasks.
However, it directly creates an instance of PersonService, which can lead to challenges in unit testing due to tight coupling.
-
class PersonController { private PersonService personService = new PersonService(); public List<Person> findAllPersons() { return personService.findAllPersons(); } } class PersonService { public List<Person> findAllPersons() { return List.of(new Person(1, "Jyotirmaya"), new Person(2, "Siva") ); } } record Person(int id, String name) {} - But if you look here, the PersonController is creating the PersonService itself. Writing unit tests for this class to verify its behavior will be challenging, although we can overcome this by using libraries like Mockito, which under the hood, utilizes reflection for testing.
- In this case, it’s recommended to pass dependencies through a constructor instead of a setter. By doing so, we can make the field immutable by declaring it as final.
-
class PersonController { private final PersonService personService; public PersonController(PersonService personService) { this.personService = personService; } public List<Person> findAllPersons() { return personService.findAllPersons(); } } class PersonService { public List<Person> findAllPersons() { return List.of(new Person(1, "Jyotirmaya"), new Person(2, "Siva") ); } } record Person(int id, String name) {}
-
How Dependency Injection works in Spring?
Before delving into Dependency Injection, let’s first understand IoC. Many people use IoC and DI interchangeably, but they are not the same in my opinion.
- Inversion of Control (IoC) in the Spring framework is a design principle where the control of object creation and lifecycle management is shifted from the application code to the Spring container.
- Spring achieves this primarily through two mechanisms i.e. DI and AOP.
- Dependency Injection (DI) is a key mechanism used by Spring to achieve IoC. With DI, the Spring container is responsible for injecting dependencies (i.e., objects) into a class when it is instantiated, rather than the class itself creating its dependencies. This promotes loose coupling between components, making the code more testable, and maintainable.
- Aspects, on the other hand, are used in Spring to implement cross-cutting concerns such as logging, security, and transaction management. Aspects enable modularization of such concerns, allowing them to be applied across multiple classes without the need for repetitive code. Spring achieves this through AOP (Aspect-Oriented Programming), which allows developers to define aspects separately from the main application logic and apply them selectively to target components using annotations or XML configuration.
@Component or other stereotype annotations(@Controller, @RestController, @Repository, @Service, @Configuration):
- First spring creates
BeanDefinitionobject and then this object used to create Bean Instance. BeanDefinition: is a blueprint for Bean Instance.- This contract has some important metadata, like
- Scope
- Factory Bean Name
- Factory Method Name
- Lazy
- Depends On
- Init Method
- Destroy Method
- Primary
- Type etc. which tells Spring What to do when create Bean Instance.
- This contract has some important metadata, like
And we have BeanDefinitionRegistry which register these BeanDefination.
Now here comes the Container which contains Bean Instances using BeanDefination, That is BeanFactory(spring-beans.jar).
There are many implementation of BeanFactory. But DefaultListableBeanFactory implements both BeanFactory and BeanDefinitionRegistry.
- This
DefaultListableBeanFactoryis an important contract in Spring containing following responsibilities- It manages Bean Definitions as well as Bean Instances
The ApplicationContext (provided by spring-context.jar) serves as an implementation of the BeanFactory, offering additional functionalities such as @Configuration, Environment, ApplicationEvent etc.
-
An effective method for creating a context for our Spring application is through
AnnotationConfigApplicationContext. While this is not the sole approach available, the Annotation-based method has become increasingly popular compared to XML configuration. As a result, it is the preferred choice for most modern Spring applications.- If you see the hierarchy of
AnnotationConfigApplicationContextwhich provides@Configuration(Meta annotated with@Component) support it looks like below
AbstractApplicationContextprovides an implementation to detect special beans likeBeanFactoryPostProcessors,BeanPostProcessorsandApplicationListeners. And GenericApplicationContext contains aDefaultListableBeanFactoryand implementsBeanDefinitionRegistrytoo. - If you see the hierarchy of
-
Scan all the packages for stereotype annotation
-
Identify the bean dependencies using Java Reflection.
-
Create instances of the bean using Reflection API