0% found this document useful (0 votes)
21 views16 pages

Spring Dependency Injection

Uploaded by

mariagemariagee5
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views16 pages

Spring Dependency Injection

Uploaded by

mariagemariagee5
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 16

Dependency Injection

Mecanism of Autowired annotation and Difference


between constructor injection and setter injection

Instructor : Dr. BADR EL KHALYLY


The @Autowired annotation in Spring Framework is used for dependency injection (DI).
Dependency injection means assigning the instanciated object by the container to a dependency.

1. Purpose
 The @Autowired annotation tells Spring to resolve and inject the dependency into the
annotated field, constructor, or method.
 It works in conjunction with Spring's ApplicationContext to locate and inject beans.

2. Mechanism

Step 1: Scanning for Beans


During the application context initialization, Spring scans the classpath for components using
annotations like @Component, @Service, @Repository, etc. or in @Bean definition.
These annotated classes are registered as Spring Beans.

Step 2: Dependency Resolution


Spring checks all classes with @Autowired annotations and resolves their dependencies by:
Matching the required type (e.g., by class or interface).
Optionally matching by name (if multiple beans of the same type are present).

Step 3: Injection
Using reflection, Spring:
Identifies the field, method, or constructor annotated with @Autowired.
Makes the annotated field accessible (if private).
Instantiates and injects the required dependency.

The injection type:

Field Injection :

@Autowired
private MyService myService;

Spring uses reflection to access the private field and inject the required bean.

Setter Injection

@Autowired
public void setMyService(MyService myService) {
this.myService = myService;
}

Spring invokes the setter method with the resolved dependency.

Constructor Injection

@Autowired
public MyComponent(MyService myService) {
this.myService = myService;
}
Constructor injection is preferred for mandatory dependencies and works well with
@RequiredArgsConstructor in Lombok.

Yes, when @Autowired is used on a constructor or when a class uses Lombok's


@RequiredArgsConstructor, the dependencies are injected at the moment of instantiation of the
bean. This is part of the constructor injection mechanism in Spring.

The reflection process used by Spring to handle @Autowired involves accessing private fields,
methods, or constructors in classes and injecting dependencies into them. Here’s a detailed
breakdown of how reflection APIs like Field.setAccessible(true) and Constructor.newInstance()
work in the context of Spring’s dependency injection:

1. Reflection Basics
Java’s Reflection API provides tools to inspect and modify classes, methods, and fields at runtime,
even if they are private. The key classes involved are:
 java.lang.reflect.Field: Allows access to fields of a class.
 java.lang.reflect.Constructor: Allows instantiation of objects via constructors.
 java.lang.reflect.Method: Allows invocation of methods.
Spring uses these APIs during dependency injection.

2. Field Injection
When Spring injects a dependency into a private field annotated with @Autowired, it uses the
following steps:
Example:

@Autowired
private MyService myService;

How Reflection Works:

The reflection process used by Spring to handle @Autowired involves accessing private fields,
methods, or constructors in classes and injecting dependencies into them. Here’s a detailed
breakdown of how reflection APIs like Field.setAccessible(true) and Constructor.newInstance()
work in the context of Spring’s dependency injection:

1. Reflection Basics
Java’s Reflection API provides tools to inspect and modify classes, methods, and fields at runtime,
even if they are private. The key classes involved are:
 java.lang.reflect.Field: Allows access to fields of a class.
 java.lang.reflect.Constructor: Allows instantiation of objects via constructors.
 java.lang.reflect.Method: Allows invocation of methods.
Spring uses these APIs during dependency injection.

2. Field Injection
When Spring injects a dependency into a private field annotated with @Autowired, it uses the
following steps:
Example:

@Autowired
private MyService myService;
How Reflection Works:
1. Retrieve the Field:
o Spring uses Class.getDeclaredField("myService") to get a reference to the field.
o Example:

Field field = clazz.getDeclaredField("myService");


2. Make the Field Accessible:
o Since private fields are not normally accessible, Spring uses Field.setAccessible(true)
to override Java’s access checks.
o Example:

field.setAccessible(true);
3. Set the Dependency:
o Spring assigns the resolved bean to the field using Field.set().
o Example:

field.set(targetObject, resolvedBean);

4. Here, targetObject is the instance of the class containing the field, and resolvedBean is the
dependency bean.

3. Setter Injection
For setter-based injection, Spring invokes the setter method with the dependency as an argument.
Example:

@Autowired
public void setMyService(MyService myService) {
this.myService = myService;
}
How Reflection Works:
1. Retrieve the Method:
o Spring uses Class.getDeclaredMethod("setMyService", MyService.class) to find the
setter method.
o Example:

Method method = clazz.getDeclaredMethod("setMyService", MyService.class);


2. Make the Method Accessible:
o If the method is private, Spring uses Method.setAccessible(true) to override access
checks.
o Example:

method.setAccessible(true);
3. Invoke the Method:
o Spring calls Method.invoke() with the target object and the resolved dependency.
o Example:

method.invoke(targetObject, resolvedBean);

4. Constructor Injection
For constructor-based injection, Spring uses the Constructor.newInstance() method to create an
instance of the class with the required dependencies.

Example:
@Autowired
public MyComponent(MyService myService) {
this.myService = myService;
}

How Reflection Works:


1. Retrieve the Constructor:
o Spring uses Class.getDeclaredConstructor(MyService.class) to get the appropriate
constructor.
o Example:

Constructor<?> constructor = clazz.getDeclaredConstructor(MyService.class);

2. Make the Constructor Accessible:


o If the constructor is private, Spring uses Constructor.setAccessible(true) to override
access checks.
o Example:

constructor.setAccessible(true);

3. Create the Object:


o Spring calls Constructor.newInstance() with the resolved dependency to create a new
object.
o Example:

Object instance = constructor.newInstance(resolvedBean);

5. Full Code Example


Here’s a simplified simulation of how Spring might inject a dependency:
Classes:

class MyService {
public void doSomething() {
System.out.println("Service is working!");
}
}

class MyComponent {
@Autowired
private MyService myService;

public void performAction() {


myService.doSomething();
}
}
Reflection Simulation:

import java.lang.reflect.Field;

public class SpringDIExample {


public static void main(String[] args) throws Exception {
// Simulate Spring Container
MyService myServiceBean = new MyService(); // Resolved dependency
MyComponent myComponent = new MyComponent();

// Inject dependency using Reflection


Field field = MyComponent.class.getDeclaredField("myService");
field.setAccessible(true); // Make private field accessible
field.set(myComponent, myServiceBean); // Set the dependency

// Test the injection


myComponent.performAction(); // Output: Service is working!
}
}

Why RequiredConstructor of Lombok using in constructor is more favored than setter injection ?

Using Lombok's @RequiredArgsConstructor (constructor-based injection) is often favored over


setter injection in modern development for several reasons, primarily related to immutability,
clarity, and design best practices.

1. Immutability and Final Fields


 Constructor-based injection allows dependencies to be declared as final, ensuring they are
initialized only once, at the time of object creation.
 This makes the class immutable in terms of its dependencies, reducing the risk of accidental
re-assignment or null values during the object's lifecycle.

Example with @RequiredArgsConstructor:

@RequiredArgsConstructor
public class MyService {
private final Dependency dependency; // Immutable and required
}

 @RequiredArgsConstructor generates a constructor for all final fields automatically.


 Setter injection doesn’t provide this guarantee, as dependencies can be modified after object
creation.

2. Mandatory Dependencies
 Constructor-based injection ensures that all required dependencies are provided at the time
of object creation. If a required dependency is missing, the code will fail to compile or throw
an error at runtime.
 With setter injection, dependencies can be optional, leading to potential issues if required
dependencies are not set explicitly.
Example:

@RequiredArgsConstructor
public class MyService {
private final Dependency dependency; // Ensures mandatory injection
}

 If the dependency is not provided, the application won’t start, ensuring the issue is caught
early.

Setter Injection Problem:

public class MyService {


private Dependency dependency;

public void setDependency(Dependency dependency) {


this.dependency = dependency;
}
}
 If the setDependency() method is not called, dependency remains null, leading to potential
NullPointerException during runtime.

3. Thread Safety
 Constructor-based injection with final fields ensures thread safety because the injected
dependencies cannot be modified after initialization.
 With setter injection, dependencies can be altered at any point, making the class vulnerable to
race conditions in multi-threaded environments.

4. Clarity and Simplicity


 Constructor injection makes dependencies explicit. By looking at the constructor, you can
immediately understand what dependencies the class requires.
 With setter injection, dependencies are scattered across setter methods, making the code
harder to understand and maintain.
Constructor Injection:

@RequiredArgsConstructor
public class MyService {
private final Dependency dependency; // Clear requirement
}

Setter Injection:

public class MyService {


private Dependency dependency;

public void setDependency(Dependency dependency) {


this.dependency = dependency; // Harder to track
}
}
5. Best Practices and Framework Recommendations
 Constructor injection is widely recommended as a best practice by frameworks like Spring
because it aligns with the principles of dependency injection (DI) and inversion of control
(IoC).
 Setter injection is considered less reliable and is often reserved for optional dependencies.

6. Testability
 Constructor-based injection improves testability, as dependencies are set up at the time of
object creation. You can easily pass mocks or stubs in tests.
 With setter injection, you need to ensure all setters are called correctly in tests, which adds
complexity.
Constructor Injection in Tests:

Dependency mockDependency = mock(Dependency.class);


MyService myService = new MyService(mockDependency); // Simple and clean
Setter Injection in Tests:

Dependency mockDependency = mock(Dependency.class);


MyService myService = new MyService();
myService.setDependency(mockDependency); // Requires additional calls

7. Reduced Boilerplate with Lombok


 Lombok’s @RequiredArgsConstructor significantly reduces boilerplate by generating
constructors automatically for all final fields, making constructor-based injection even more
convenient and appealing.

Without Lombok:

public class MyService {


private final Dependency dependency;

public MyService(Dependency dependency) {


this.dependency = dependency;
}
}

With Lombok:

@RequiredArgsConstructor
public class MyService {
private final Dependency dependency;
}

8. Flexibility for Optional Dependencies


While constructor injection is preferred for mandatory dependencies, optional dependencies can still
be injected via setters or marked with @Autowired(required = false).
Example:

@RequiredArgsConstructor
public class MyService {
private final Dependency mandatoryDependency;

@Autowired(required = false)
private OptionalDependency optionalDependency;
}

Summary: Why Constructor Injection is Preferred

Aspect Constructor Injection Setter Injection


Ensures dependencies are final and Dependencies can be modified post-
Immutability
immutable. creation.
Mandatory Guarantees all required dependencies are Relies on developers to set
Checks provided. dependencies.
Dependencies can be altered,
Thread Safety Safer in multi-threaded environments.
introducing risk.
Explicitly shows required dependencies in Requires scanning setters for
Clarity
the constructor. dependencies.
Framework Recommended by Spring and other DI Considered less reliable for
Support frameworks. required deps.
Requires additional setup for
Testability Easy to test with mocks and stubs.
testing.
Boilerplate Reduced with Lombok. Requires additional setter methods.

Detailed Differences Between Constructor and Setter Injection

Aspect Constructor Injection Setter Injection


When Dependencies During the instantiation phase After instantiation, during the dependency
Are Injected (step 1). injection phase (step 2).
A no-argument constructor creates the
Constructor is used to create the
Bean Creation bean, then setters are used to inject
bean with dependencies.
dependencies.
Optional dependencies are supported, and
Mandatory Enforces all required
missing dependencies won’t prevent bean
Dependencies dependencies at bean creation.
creation.
Not required. Spring uses the Required. A no-argument constructor is
Default Constructor
parameterized constructor. necessary.
Ensures dependencies are not
Dependencies might remain null if setters
Null-Safety null (constructor-level
are not called.
validation).
Encourages immutability by
Immutability Fields cannot be final since setters are used.
allowing final fields.
Error Detection Missing dependencies cause Errors might occur later at runtime if
Aspect Constructor Injection Setter Injection
errors at startup (early dependencies are not set.
detection).
Dependencies cannot be re- Dependencies can be re-injected, allowing
Re-Injectability
injected after instantiation. more flexibility but less safety.
Thread-safe due to immutability Less thread-safe since dependencies can be
Thread Safety
of dependencies. modified post-creation.

Practical Differences in Code and Usage


Constructor Injection:

@Component
public class MyService {
private final Dependency dependency;

public MyService(Dependency dependency) {


this.dependency = dependency; // Enforced at bean creation
}
}
 Pros:
o Ensures the bean is created with all required dependencies.
o Guarantees null-safety.
o Follows good design principles by making dependencies explicit.
 Cons:
o Not suitable for optional dependencies.
o Complex beans with many dependencies might require large constructors.
Setter Injection:

@Component
public class MyService {
private Dependency dependency;

public MyService() {
// Default constructor
}

@Autowired
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
}
 Pros:
o Suitable for optional dependencies.
o Easier to manage for beans with many dependencies.
 Cons:
o Allows dependencies to be null if setters are not called.
o Fields cannot be final, reducing immutability.
o Dependencies can be changed post-instantiation, which can lead to bugs.

6. Initialization Timing and Impact


 Constructor Injection:
o Dependencies must be resolved before the bean is fully instantiated.
o If any dependency is missing or unresolved, the application will fail at startup.
 Setter Injection:
o The bean can be instantiated without dependencies.
o Missing or unresolved dependencies can result in runtime errors later.

7. Real-World Usage
 Constructor Injection:
o Preferred for mandatory dependencies.
o Used in modern applications due to its alignment with immutability and best practices.
o Often combined with Lombok’s @RequiredArgsConstructor.
 Setter Injection:
o Used for optional dependencies.
o Common in legacy applications or scenarios requiring flexibility.

Immutability: The notificationService field is final, preventing accidental re-assignment.


Early Error Detection: Missing or unresolved dependencies are detected at startup, avoiding
potential runtime errors.

An example of re-assignment occurs when a field is not final, and its value is accidentally or
deliberately changed after the object has been instantiated. This can lead to unexpected behavior or
bugs, especially when the field represents a dependency that the object relies on.
Here’s a detailed example:

1. Without final Field (Allowing Re-assignment)


Example Code:

@Component
public class MyService {
private NotificationService notificationService;

@Autowired
public MyService(NotificationService notificationService) {
this.notificationService = notificationService;
}

public void performTask() {


System.out.println("Using: " + notificationService.getName());
}

// A method that accidentally reassigns the dependency


public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}
}
Scenario:
 A malicious or accidental call to setNotificationService() can replace the existing
notificationService dependency.
Main Application:

@Component
public class AlternativeNotificationService implements NotificationService {
@Override
public String getName() {
return "AlternativeNotificationService";
}
}

@Component
public class DefaultNotificationService implements NotificationService {
@Override
public String getName() {
return "DefaultNotificationService";
}
}

@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);

// Retrieve MyService bean


MyService myService = context.getBean(MyService.class);

// Perform task (uses default NotificationService)


myService.performTask(); // Output: Using: DefaultNotificationService

// Simulate re-assignment
NotificationService alternativeService = context.getBean(AlternativeNotificationService.class);
myService.setNotificationService(alternativeService);

// Perform task again (uses alternative NotificationService)


myService.performTask(); // Output: Using: AlternativeNotificationService
}
}

Problem:
 The notificationService dependency is re-assigned, potentially introducing inconsistencies.
 This may break the application's logic if the new dependency behaves differently than
expected.
2. With final Field (Preventing Re-assignment)
Example Code:

@Component
public class MyService {
private final NotificationService notificationService;

@Autowired
public MyService(NotificationService notificationService) {
this.notificationService = notificationService; // Set only once
}

public void performTask() {


System.out.println("Using: " + notificationService.getName());
}
}

Main Application:

@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);

// Retrieve MyService bean


MyService myService = context.getBean(MyService.class);

// Perform task (uses default NotificationService)


myService.performTask(); // Output: Using: DefaultNotificationService

// Attempt to reassign (not possible since notificationService is final)


// myService.notificationService = context.getBean(AlternativeNotificationService.class); //
Compile-time error
}
}

Benefit:
 The final modifier ensures that notificationService can only be assigned during construction.
 Any attempt to re-assign the field results in a compile-time error, eliminating the risk of
runtime bugs caused by accidental re-assignment.

3. Key Points
 Without final:
o Fields are mutable, allowing re-assignment after the object is created.
o This can lead to bugs if the state of the object changes unpredictably.
 With final:
o Fields are immutable, ensuring they are assigned once and never changed.
o This makes the object safer, especially in multithreaded or shared contexts.
Here’s an example demonstrating thread safety in the context of constructor-based injection and
setter-based injection in Java:
Constructor-based injection (Thread-Safe Example)
In constructor-based injection, dependencies are provided when the object is created and cannot be
changed afterwards because they are typically assigned to final fields. This ensures that the object is
immutable and thread-safe.

public class ThreadSafeService {

private final SomeDependency dependency;

// Constructor-based injection ensures that the dependency is set only once


public ThreadSafeService(SomeDependency dependency) {
this.dependency = dependency;
}

// Method that uses the injected dependency


public void performTask() {
synchronized (this) {
// Thread-safe access to dependency
dependency.doWork();
}
}
}

class SomeDependency {
public void doWork() {
// Simulating work
System.out.println("Work done");
}
}

public class Main {


public static void main(String[] args) {
SomeDependency dependency = new SomeDependency();
ThreadSafeService service = new ThreadSafeService(dependency);

// Multiple threads accessing the service


Runnable task = () -> service.performTask();

Thread t1 = new Thread(task);


Thread t2 = new Thread(task);

t1.start();
t2.start();
}
}
Explanation:
 In this example, SomeDependency is injected via the constructor, and the dependency field is
marked as final, ensuring that it cannot be changed after initialization.
 Multiple threads can safely access the performTask() method because the injected
dependency is immutable, preventing race conditions or changes during execution.
Setter-based injection (Not Thread-Safe Example)
With setter-based injection, dependencies can be changed after object creation, which introduces the
risk of modifying dependencies in a multi-threaded environment.

public class NotThreadSafeService {

private SomeDependency dependency;

// Setter-based injection allows changing the dependency at any time


public void setDependency(SomeDependency dependency) {
this.dependency = dependency;
}

// Method that uses the injected dependency


public void performTask() {
synchronized (this) {
// Thread-safe access to dependency (though dependency can be modified)
dependency.doWork();
}
}
}

class SomeDependency {
public void doWork() {
// Simulating work
System.out.println("Work done");
}
}

public class Main {


public static void main(String[] args) {
NotThreadSafeService service = new NotThreadSafeService();
SomeDependency dependency = new SomeDependency();
service.setDependency(dependency);

// Multiple threads accessing the service


Runnable task = () -> service.performTask();

Thread t1 = new Thread(task);


Thread t2 = new Thread(task);

t1.start();
t2.start();
}
}
Explanation:
 In this case, the dependency is set via a setter method, allowing it to be changed at any point
during the object's lifecycle.
 If the setDependency() method is called concurrently from multiple threads, the dependency
might be modified while other threads are using it, causing potential race conditions or
inconsistent behavior.
Key Points:
 Constructor-based injection ensures that the object's state is immutable after creation,
making it thread-safe in concurrent environments.
 Setter-based injection allows dependencies to be changed at runtime, making it more
vulnerable to race conditions if not handled carefully.

The thread safety in the constructor-based injection example is partly achieved by using the final
keyword for the injected dependencies. Let's break it down further:
Why final Ensures Thread Safety:
 The final keyword in Java indicates that a field can only be assigned once. Once the
dependency is injected via the constructor, the final field cannot be reassigned. This
guarantees that the object’s state is immutable after initialization, making it safe to access
from multiple threads without worrying about the field being changed during execution.
Key Points:
1. Immutability: When you mark a dependency as final, you ensure that the object cannot be
reinitialized after the object is constructed. This means there’s no risk of another thread
changing the dependency while other threads are using it.
2. Thread-Safety in Concurrent Environments: In multi-threaded environments, if an object’s
state can change (like with setter-based injection), you might encounter race conditions
where multiple threads modify the object’s state at the same time. With final fields, this risk is
eliminated for the injected dependencies because they can’t be modified after the constructor
has completed.
Example Recap:

public class ThreadSafeService {

private final SomeDependency dependency; // This is immutable and thread-safe.

public ThreadSafeService(SomeDependency dependency) {


this.dependency = dependency; // Injected once and never changed
}

public void performTask() {


synchronized (this) {
// Accessing the dependency is safe because it cannot change after construction
dependency.doWork();
}
}
}

In this example, the dependency is immutable because of the final keyword, ensuring that no other
thread can change it after it's been set in the constructor. This helps avoid race conditions where the
state of the dependency might change unexpectedly.
In contrast, with setter injection, there’s no guarantee that the dependency will remain constant over
time, making it more prone to issues in concurrent scenarios.
Additional Thread Safety Measures:
 Synchronization: Although final ensures the dependency can't be changed, using
synchronized or other mechanisms (like volatile or atomic operations) might still be
necessary if the method involves shared mutable state (like updating variables or performing
operations that could affect other threads).

You might also like