Lifecycle Steps: NotificationServiceClient vs. AzureBlobStorageClient

Both classes are Spring-managed Beans, but they follow slightly different lifecycles due to how they receive dependencies.

package com.fepatex.offermodule.integration;

import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobContainerClientBuilder;

import jakarta.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.InputStream;
/**
 * AzureBlobStorageClient is a Spring-managed component responsible for interacting with
 * Azure Blob Storage. It initializes a {@link BlobContainerClient} to handle blob operations.
 *
 * <p>This class uses an empty constructor and a {@link PostConstruct} method to initialize
 * its {@link BlobContainerClient}. This approach is required because Spring injects the
 * {@link Value}-annotated fields after the constructor is called. Using {@link PostConstruct}
 * ensures that these values are available before the client is initialized.</p>
 */
@Component
public class AzureBlobStorageClient {

    private BlobContainerClient containerClient;

    @Value("${azure.storage.connection-string}")
    private String connectionString;

    @Value("${azure.storage.container-name}")
    private String containerName;

     /**
     * Default constructor. This is intentionally left empty to allow Spring to manage the
     * lifecycle of the bean. Field values annotated with @Value are injected after
     * the constructor call, so initialization logic is deferred to the @PostConstruct method.
     */
    public AzureBlobStorageClient() {
        // Empty constructor
    }

     /**
     * Initializes the {@link BlobContainerClient} after all {@link Value}-annotated fields
     * have been injected by Spring. This method ensures the client is configured correctly
     * with the injected properties.
     *
     * <p>Marked with {@link PostConstruct} to guarantee that it runs after dependency injection
     * is complete, avoiding potential null reference issues in the constructor.</p>
     */
    @PostConstruct
    public void initializeBlobContainerClient() {

        this.containerClient = new BlobContainerClientBuilder()
                .connectionString(connectionString)
                .containerName(containerName)
                .buildClient();
    }

    public String uploadFile(String fileName, InputStream fileData, long fileSize) {
        // Get a Blob Client
        BlobClient blobClient = containerClient.getBlobClient(fileName);

        // Upload file
        blobClient.upload(fileData, fileSize, true);

        // Return the URL of the uploaded blob
        return blobClient.getBlobUrl();
    }
}

package com.fepatex.offermodule.integration;

import com.fepatex.offermodule.dto.common.NotificationRequestDTO;
import com.fepatex.offermodule.exception.common.NotificationServiceException;

import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;

@Component
public class NotificationServiceClient {

    private final RestClient restClient;
    
    private static final String NOTIFICATION_SERVICE_BASE_URL =
            "<http://fepatex-notification-ms-service.fepatex-notification-ms.svc.cluster.local:80>";

    public NotificationServiceClient(RestClient.Builder restClientBuilder) {
        this.restClient = restClientBuilder
                .baseUrl(NOTIFICATION_SERVICE_BASE_URL)
                .build();
    }

    /**
     * Sends a notification request to the notification service.
     *
     * @param notificationRequestDTO the notification request payload
     * @throws NotificationServiceException if the request fails
     */
    public void sendNotification(NotificationRequestDTO notificationRequestDTO) {
        try {
            restClient
                .post()
                .uri("/api/v1/notifications/offer-creation")
                .body(notificationRequestDTO)
                .retrieve()
                .toBodilessEntity();
        } catch (RestClientException e) {
            throw new NotificationServiceException("Failed to send notification: " + e.getMessage(), e);
        }
    }
}


🚀 Lifecycle Steps for NotificationServiceClient

This class uses constructor injection, so its dependencies are fully initialized during Bean instantiation.

🔹 1. ApplicationContext Load Phase

🔹 2. Bean Instantiation Phase

🔹 3. Dependency Injection Phase