Chuyển đến nội dung chính

Lesson 3: Auto-Configuration, Spring IoC Container & Application Properties

How does the Auto-Configuration mechanism work? IoC Containers, ApplicationContext, BeanFactory. Configure the application with application.properties/yaml and Profile.

💻 Programming — Lesson 2 Lesson 3: Auto-Configuration, Spring IoC Containers & Application Properties

Spring Boot 4: From Basics to Advanced

Part 1: Spring Boot Platform

xdev.asia

Introduction

Auto-Configuration is the "magic" that makes Spring Boot different from pure Spring Framework. Understanding how it works will help you debug effectively and customize the application as desired. In this article, we will explore IoC Containers, the Auto-Configuration mechanism, and how to manage application configuration.


1. Inversion of Control (IoC) Containers

1.1 What is IoC?

In traditional programming, your code creates and manages objects:

// Truyền thống: bạn tạo dependencies
public class OrderService {
    private final OrderRepository repository = new OrderRepository();
    private final EmailService emailService = new EmailService();
}

With IoC, the framework manages the creation of objects:

// IoC: framework inject dependencies
@Service
public class OrderService {
    private final OrderRepository repository;
    private final EmailService emailService;

    // Spring tự động inject các dependencies
    public OrderService(OrderRepository repository, EmailService emailService) {
        this.repository = repository;
        this.emailService = emailService;
    }
}

1.2 ApplicationContext vs BeanFactory

Spring IoC Container has 2 main interfaces:

BeanFactory (interface)
  └── ApplicationContext (interface, extends BeanFactory)
        ├── AnnotationConfigApplicationContext
        ├── GenericWebApplicationContext
        └── ...
FeaturesBeanFactoryApplicationContext
Bean instantiationLazyEager (default)
Event publishingNoYes
AOP supportLimitationsFull
InternationalizationNoYes
Environment abstractionNoYes

In fact, always use ApplicationContext.

1.3 Bean Registration

// Cách 1: Stereotype Annotations (phổ biến nhất)
@Component      // Generic component
@Service        // Business logic
@Repository     // Data access
@Controller     // Web controller
@RestController // REST API controller
@Configuration  // Configuration class

// Cách 2: @Bean method trong @Configuration
@Configuration
public class AppConfig {

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }

    @Bean
    public RestClient restClient() {
        return RestClient.builder()
            .baseUrl("https://api.example.com")
            .build();
    }
}

2. Auto-Configuration Deep Dive

2.1 Mechanism of action

Auto-Configuration is based on assessment of conditions at startup time:

Application Start
    │
    ▼
@SpringBootApplication
    │
    ├── @EnableAutoConfiguration
    │       │
    │       ▼
    │   META-INF/spring/
    │   org.springframework.boot.autoconfigure.AutoConfiguration.imports
    │       │
    │       ▼
    │   Evaluate @Conditional annotations
    │       │
    │       ├── @ConditionalOnClass → Class có trong classpath?
    │       ├── @ConditionalOnMissingBean → Bean đã tồn tại chưa?
    │       ├── @ConditionalOnProperty → Property có được set?
    │       └── @ConditionalOnWebApplication → Có phải web app?
    │       │
    │       ▼
    │   Create & Register Beans
    │
    ▼
Application Ready

2.2 Example: DataSource Auto-Configuration

When you add spring-boot-starter-data-jpa and PostgreSQL driver:

// Spring Boot tự động detect và tạo configuration tương tự:
@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }
}

Spring Boot will:

  1. Detection DataSource.class in classpath → @ConditionalOnClass = true
  2. Inspection not yet available DataSource beans → @ConditionalOnMissingBean = true
  3. Read properties spring.datasource.* → Create DataSource

2.3 View Auto-Configuration Report

# Bật debug mode để xem report
debug=true
./mvnw spring-boot:run

Output will display:

============================
CONDITIONS EVALUATION REPORT
============================

Positive matches:
-----------------
   DataSourceAutoConfiguration matched:
      - @ConditionalOnClass found required classes
        'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'

Negative matches:
-----------------
   MongoAutoConfiguration:
      - @ConditionalOnClass did not find required class
        'com.mongodb.client.MongoClient'

2.4 Customize or Disable Auto-Configuration

// Exclude cụ thể
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    SecurityAutoConfiguration.class
})
public class MyApplication { }

// Hoặc trong properties
spring.autoconfigure.exclude=\
  org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

3. Application Properties & YAML

3.1 Properties vs YAML

application.properties:

server.port=8080
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=admin
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update

application.yaml (or .yml):

server:
  port: 8080

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/mydb
    username: admin
    password: secret
  jpa:
    hibernate:
      ddl-auto: update

Both formats are supported. YAML is more readable for nested configuration, but properties are more common.

3.2 Custom Properties

// Định nghĩa properties class
@ConfigurationProperties(prefix = "app")
public record AppProperties(
    String name,
    String version,
    Security security
) {
    public record Security(
        String jwtSecret,
        long jwtExpirationMs,
        long refreshExpirationMs
    ) {}
}
# application.yaml
app:
  name: My Application
  version: 1.0.0
  security:
    jwt-secret: my-256-bit-secret-key-for-jwt-signing
    jwt-expiration-ms: 3600000
    refresh-expiration-ms: 86400000
// Enable và sử dụng
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class MyApplication { }

@Service
public class AuthService {
    private final AppProperties appProperties;

    public AuthService(AppProperties appProperties) {
        this.appProperties = appProperties;
        // appProperties.security().jwtSecret()
    }
}

3.3 Property Sources & Priorities

Spring Boot reads properties from many sources with priority order:

1. Command line arguments (highest priority)
   java -jar app.jar --server.port=9090

2. OS Environment variables
   SERVER_PORT=9090

3. application-{profile}.properties
   application-prod.properties

4. application.properties
   src/main/resources/application.properties

5. @PropertySource annotations

6. Default properties (lowest priority)

4. Profiles — Configuration according to environment

4.1 Definition of Profiles

# application.yaml (default, mọi môi trường)
spring:
  application:
    name: my-app

---
# application-dev.yaml
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:postgresql://localhost:5432/mydb_dev
  jpa:
    show-sql: true

---
# application-prod.yaml
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:postgresql://db-prod:5432/mydb_prod
  jpa:
    show-sql: false

4.2 Activate Profile

# Cách 1: Command line
java -jar app.jar --spring.profiles.active=prod

# Cách 2: Environment variable
SPRING_PROFILES_ACTIVE=prod java -jar app.jar

# Cách 3: Trong application.properties
spring.profiles.active=dev

# Cách 4: Trong IDE
# Run Configuration → Environment variables → SPRING_PROFILES_ACTIVE=dev

4.3 Profile-specific Beans

@Configuration
public class StorageConfig {

    @Bean
    @Profile("dev")
    public StorageService localStorageService() {
        return new LocalStorageService("/tmp/uploads");
    }

    @Bean
    @Profile("prod")
    public StorageService s3StorageService() {
        return new S3StorageService();
    }
}

5. Externalized Configuration Best Practices

5.1 No hardcode secrets

# SAI - Không bao giờ commit secrets
spring:
  datasource:
    password: my-real-password

# ĐÚNG - Dùng environment variables
spring:
  datasource:
    password: ${DB_PASSWORD}

5.2 Using .env files for local development

# .env (thêm vào .gitignore!)
DB_PASSWORD=local-dev-password
JWT_SECRET=dev-secret-key

5.3 Validation for properties

@ConfigurationProperties(prefix = "app")
@Validated
public record AppProperties(
    @NotBlank String name,
    @NotBlank String version,
    @Valid Security security
) {
    public record Security(
        @NotBlank String jwtSecret,
        @Positive long jwtExpirationMs
    ) {}
}

The application will fail-fast when required properties are missing.


Summary

  • IoC Container (ApplicationContext) manages the lifecycle of beans, supports Dependency Injection via constructor/setter/field injection
  • Auto-Configuration automatically configures beans based on classpath dependencies and conditional annotations
  • Spring Boot supports both .properties and .yaml format, Profiles allows configuration according to environment (dev/staging/prod)

Exercises

  1. Turn on debug=true in application.properties, run the application and analyze the 5 auto-configurations that are matched (Positive matches)
  2. Create AppProperties record class with prefix app, bind 3 custom properties from application.yaml and inject into a controller to display
  3. Create 2 profiles (dev and prod) with different database URLs, run the application with each profile and verify log output