DEV Community

Hunor Vadasz-Perhat
Hunor Vadasz-Perhat

Posted on

spring-006: logical-execution-order-and-code-flow

public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(TenantConfig.class);
    TenantService tenantService = context.getBean(TenantService.class);
    tenantService.processTenantData();
}

@Configuration
@ComponentScan(basePackages = "org.example4") // Adjust to your package structure
public class TenantConfig {

    @Bean(name = "tenantA-dataSource")
    public TenantDataSource tenantADataSource() {
        return new TenantDataSource();
    }

    @Bean(name = "tenantB-dataSource")
    public TenantDataSource tenantBDataSource() {
        return new TenantDataSource();
    }
}

public class TenantDataSource implements BeanNameAware {

    private String tenantName;
    private String databaseUrl;

    @Override
    public void setBeanName(String beanName) {
        // Extract tenant name from the bean name
        if (beanName.contains("-")) {
            this.tenantName = beanName.split("-")[0];
        } else {
            throw new IllegalArgumentException("Invalid bean naming convention. Expected format: <tenantName>-dataSource");
        }

        // Assign a database URL dynamically based on the tenant name
        this.databaseUrl = "jdbc:mysql://localhost:3306/" + tenantName + "_db";
    }

    public void connect() {
        System.out.println("Connecting to database for tenant: " + tenantName);
        System.out.println("Using database URL: " + databaseUrl);
        // Simulate connection logic here
    }
}

@Service
public class TenantService {

    private final TenantDataSource tenantADataSource;
    private final TenantDataSource tenantBDataSource;

    @Autowired
    public TenantService(
            @Qualifier("tenantA-dataSource") TenantDataSource tenantADataSource,
            @Qualifier("tenantB-dataSource") TenantDataSource tenantBDataSource
    ) {
        this.tenantADataSource = tenantADataSource;
        this.tenantBDataSource = tenantBDataSource;
    }

    public void processTenantData() {
        System.out.println("Processing data for all tenants...");
        tenantADataSource.connect();
        tenantBDataSource.connect();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step-by-Step Execution Order

1. Main Class Execution Begins

public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(TenantConfig.class);
    TenantService tenantService = context.getBean(TenantService.class);
    tenantService.processTenantData();
}
Enter fullscreen mode Exit fullscreen mode
  • What happens here:
    • A new Spring application context (AnnotationConfigApplicationContext) is initialized with TenantConfig as the configuration class.
    • Spring begins scanning for beans and components, following the configuration provided in TenantConfig.

2. Spring Processes TenantConfig

@Configuration
@ComponentScan(basePackages = "org.example4")
public class TenantConfig {
    @Bean(name = "tenantA-dataSource")
    public TenantDataSource tenantADataSource() {
        return new TenantDataSource();
    }

    @Bean(name = "tenantB-dataSource")
    public TenantDataSource tenantBDataSource() {
        return new TenantDataSource();
    }
}
Enter fullscreen mode Exit fullscreen mode
  • What happens here:
    1. @Configuration:
      • Marks TenantConfig as a configuration class.
    2. @ComponentScan:
      • Spring scans the org.example4 package for classes annotated with @Component, @Service, @Repository, etc.
      • It detects and registers the TenantService class as a Spring-managed bean because of the @Service annotation.
    3. @Bean Definitions:
      • Spring calls the tenantADataSource() and tenantBDataSource() methods to create two instances of TenantDataSource.
      • These beans are registered in the Spring context with the names:
      • tenantA-dataSource
      • tenantB-dataSource

3. Spring Instantiates TenantDataSource Beans

public class TenantDataSource implements BeanNameAware {
    @Override
    public void setBeanName(String beanName) { ... }
}
Enter fullscreen mode Exit fullscreen mode
  • What happens here:

    • Spring creates two instances of TenantDataSource by calling the methods in TenantConfig:
    • tenantADataSource() -> Creates an instance of TenantDataSource and registers it as tenantA-dataSource.
    • tenantBDataSource() -> Creates another instance of TenantDataSource and registers it as tenantB-dataSource.
  • Bean Lifecycle - BeanNameAware:

    • Spring detects that TenantDataSource implements BeanNameAware.
    • For each bean:
    • setBeanName("tenantA-dataSource") is called on the first instance.
      • tenantName = "tenantA"
      • databaseUrl = "jdbc:mysql://localhost:3306/tenantA_db"
    • setBeanName("tenantB-dataSource") is called on the second instance.
      • tenantName = "tenantB"
      • databaseUrl = "jdbc:mysql://localhost:3306/tenantB_db"

4. Spring Creates and Configures TenantService

@Service
public class TenantService {
    @Autowired
    public TenantService(
        @Qualifier("tenantA-dataSource") TenantDataSource tenantADataSource,
        @Qualifier("tenantB-dataSource") TenantDataSource tenantBDataSource
    ) {
        ...
    }
}
Enter fullscreen mode Exit fullscreen mode
  • What happens here:

    1. Spring detects TenantService because of the @Service annotation and registers it as a Spring-managed bean.
    2. Spring finds the constructor annotated with @Autowired and resolves the dependencies:
      • For the first parameter, @Qualifier("tenantA-dataSource") ensures Spring injects the bean named tenantA-dataSource.
      • For the second parameter, @Qualifier("tenantB-dataSource") ensures Spring injects the bean named tenantB-dataSource.
    3. Spring calls the constructor of TenantService:
     new TenantService(tenantADataSource, tenantBDataSource);
    
  1. The TenantService bean is now fully initialized and managed by Spring.

5. Retrieve the Spring-Managed TenantService Bean

TenantService tenantService = context.getBean(TenantService.class);
Enter fullscreen mode Exit fullscreen mode
  • What happens here:
    • The context.getBean(TenantService.class) call retrieves the TenantService bean from the Spring context.
    • This bean is already fully configured by Spring, with its dependencies (tenantADataSource and tenantBDataSource) injected.

6. Call TenantService.processTenantData()

tenantService.processTenantData();
Enter fullscreen mode Exit fullscreen mode
  • What happens here:

    • The processTenantData() method is invoked on the TenantService bean.
    • Inside the method:
      tenantADataSource.connect() is called:

      • Outputs:
       Connecting to database for tenant: tenantA
       Using database URL: jdbc:mysql://localhost:3306/tenantA_db
      

    tenantBDataSource.connect() is called:

    • Outputs:

       Connecting to database for tenant: tenantB
       Using database URL: jdbc:mysql://localhost:3306/tenantB_db
      

Summary of the Execution Order

  1. Main Class Initialization:

    • Spring context is initialized with TenantConfig.
  2. Bean Registration:

    • Spring scans TenantConfig:
      • Registers two beans: tenantA-dataSource and tenantB-dataSource.
      • Registers TenantService as a Spring-managed bean.
  3. Bean Creation and Lifecycle:

    • Spring creates TenantDataSource beans and calls setBeanName() to configure them dynamically.
    • Spring creates the TenantService bean, injecting the correct TenantDataSource beans using @Qualifier.
  4. Retrieve TenantService Bean:

    • The TenantService bean is retrieved from the Spring context.
  5. Method Execution:

    • The processTenantData() method is called, connecting to both tenant databases and printing connection details.

Key Takeaways

  • Spring Context: The Spring IoC container is responsible for creating, initializing, and managing beans.
  • Dependency Injection: Spring resolves dependencies using @Autowired and @Qualifier.
  • Bean Lifecycle: The TenantDataSource beans are initialized with tenant-specific properties during the setBeanName() phase.
  • Spring-Managed Beans: The TenantService bean is fully managed by Spring, and all dependencies are injected automatically.

Top comments (0)