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)