Spring Boot/Module 02 — Tổng kết & cheat sheet
~15 phútSpring Boot FoundationsMiễn phí lượt xem

Module 02 — Tổng kết & cheat sheet

Recap, cheat sheet 1 trang, glossary, pitfall tổng hợp, self-assessment outcomes. 1 trang để bookmark.

TL;DR: Module 02 đã bóc tách 5 trụ cột Spring Boot: bài toán Boot giải năm 2014, starter/BOM anatomy, auto-configuration chuỗi sự kiện từ classpath đến bean, 17 PropertySource ưu tiên, profile toggle bean theo môi trường, và logging stack JSON production-grade. Mini-challenge bài 07 chứng minh "5 trụ cột cộng lại = app chạy từ zero trong 60 giây". Đây là 1 trang để bookmark — quay lại khi gặp bug autoconfig, config không có effect, hay log không xuất hiện. Cheat sheet 18 dòng + glossary 18 thuật ngữ + 10 pitfall code sai/đúng + self-assessment 6 outcomes match _meta.json.

Đã đi qua những gì

Hành trình bắt đầu từ Spring 4 era: mỗi project mới tốn 60-90 phút boilerplate — khai báo 15+ dependency với version thủ công, viết web.xml, applicationContext.xml 30-100 dòng, cài Tomcat external, config log4j.xml. Phil Webb và Dave Syer ở Pivotal đề xuất giải pháp năm 2014: gom 5 thao tác đó thành defaults thông minh với cơ chế override formal. Kết quả là Spring Boot 1.0 — 18 dòng code thay thế 60 phút boilerplate.

Bài 02 bóc starter đến tận vật lý: mở jar spring-boot-starter-web ra thấy không có class — chỉ có pom.xml. Giá trị thực của starter không phải code mà là BOM spring-boot-dependencies quản version 200+ thư viện đã test tương thích. Upgrade Boot = upgrade toàn ecosystem nhất quán trong 1 dòng.

Bài 03 là insight lớn nhất module: AutoConfigurationImportSelector đọc file AutoConfiguration.imports từ mọi jar trên classpath, lấy 143 candidate, filter qua @ConditionalOn* (ASM bytecode reader, không load class), chỉ ~30-50 autoconfig register. @ConditionalOnMissingBean là cơ chế "user override default" — đăng ký bean của bạn, Boot tự rút lui.

Bài 04-05 bóc configuration và profile: 17 PropertySource theo ưu tiên (command line thắng env var thắng file), relax binding tự map SPRING_DATASOURCE_URLspring.datasource.url, @ConfigurationProperties record type-safe + @Validated fail-fast. Profile không chỉ override file mà còn toggle bean qua @Profile — kết hợp với profile groups Boot 2.4+ là pattern DRY cho multi-environment deployment.

Bài 06 chốt logging stack: SLF4J facade + Logback impl + bridge libs. Boot 3.4 GA thêm structured logging ECS 1 dòng property. MDC correlation ID trace request xuyên layer. Dynamic log level runtime qua Actuator — không cần restart app để bật DEBUG production.

Bài 07 mini-challenge — bạn build app trace đầy đủ startup → request → response bằng BeanPostProcessor + LoggingFilter MDC. Đây là template production-grade bạn dùng lại trong mọi module tiếp theo.

🗺️ Cheat sheet

ConceptKhi nào dùngPitfall thường gặp
spring-boot-starter-parent90% project — 1 dòng <parent> setup full BOM + plugin defaultsProject đã có corporate parent → không dùng được; dùng import scope thay
BOM spring-boot-dependenciesProject có corporate parent, cần Boot version quảnPhải config Java version và plugin defaults thủ công
Khai báo dep không <version>Mọi dep Spring/Boot trong Boot projectVersion explicit override BOM → mismatch toàn ecosystem
@SpringBootApplicationClass main entry pointĐặt sai package → @ComponentScan không quét đủ bean
SpringApplication.run()Entry point app — 14 bước, bước 12 là refresh()Khởi tạo bean quá nặng trong @PostConstruct → startup chậm; dùng ApplicationReadyEvent thay
AutoConfiguration.importsFile liệt kê autoconfig trong jar starter customQuên file này → autoconfig class không bao giờ load
@ConditionalOnClassAutoconfig chỉ register khi class có classpathDùng value=SomeClass.class trong autoconfig → compile fail nếu class không có; dùng name="..." string
@ConditionalOnMissingBeanDefault bean — rút lui khi user overrideThiếu annotation → ambiguous bean conflict khi user register cùng type
/actuator/conditionsDebug "tại sao bean không tạo"Không expose endpoint → không debug được; bật ít nhất trong dev
@ConfigurationProperties recordGroup config cùng prefix, type-safeQuên @ConfigurationPropertiesScan → record không bind, inject fail
@Validated + @NotBlankFail-fast khi thiếu config bắt buộcKhông validate → field null → NPE runtime sau startup
Relax bindingK8s SPRING_DATASOURCE_URLspring.datasource.urlEnv var có dấu . không work trong bash shell; dùng UPPER_SNAKE_CASE
application-prod.ymlOverride config cho profile prodHardcode secret → security incident; dùng ${DB_PASSWORD} reference env var
Profile groupsGom logic prod = prod-db + prod-cacheTypo profile name → silent fallback default, bug ngầm
@Profile("prod & monitoring")Bean chỉ register khi cả 2 profile active& operator cần Spring 6 / Boot 3+; dùng array {"prod","monitoring"} cho OR
log.error("failed", e)Log exception với stack trace đầy đủlog.error("failed: " + e.getMessage()) mất stack trace
MDC correlation IDTrace request xuyên layer bằng requestIdQuên MDC.clear() trong finally → requestId leak sang request khác
/actuator/loggers POSTDynamic log level runtime không restartKhông bảo vệ endpoint → attacker bật DEBUG, log volume DDoS

📖 Glossary module

Thuật ngữĐịnh nghĩa 1 câuNguồn
Spring Boot5 lớp đóng gói trên Spring Framework: starter, auto-config, embedded server, production features, build pluginBài 01
StarterJar rỗng class chỉ chứa pom.xml liệt kê transitive deps — cú pháp ngắn khai báo nhóm depBài 02
BOM (Bill of Materials)File pom chỉ có dependencyManagement định nghĩa version cho 200+ lib — không pull libBài 02
spring-boot-dependenciesBOM chính của Boot — Pivotal test mọi tổ hợp version tương thích trước releaseBài 02
spring-boot-starter-parentParent pom inherit BOM + thêm Java version default, UTF-8, plugin defaultsBài 02
@EnableAutoConfigurationAnnotation kích hoạt AutoConfigurationImportSelector load toàn bộ autoconfigBài 03
AutoConfigurationImportSelectorClass implement DeferredImportSelector — đọc AutoConfiguration.imports, filter, registerBài 03
AutoConfiguration.importsFile text liệt kê fully-qualified class name của mọi autoconfig trong 1 jarBài 03
@ConditionalOnMissingBeanCondition: register bean chỉ khi user chưa register cùng type — cơ chế "rút lui" của BootBài 03
@ConditionalOnClassCondition: register bean chỉ khi class tên này có trong classpathBài 03
PropertySourceNguồn cung cấp property cho Spring Environment — Boot có 17 nguồn theo thứ tự ưu tiênBài 04
Relax bindingCơ chế Boot tự convert giữa kebab-case / camelCase / UPPER_SNAKE_CASE khi bind propertyBài 04
@ConfigurationPropertiesAnnotation bind group property cùng prefix vào POJO/record — type-safe, validate đượcBài 04
ProfileNamed tag gắn vào bean hoặc config — Boot toggle registration theo profile activeBài 05
Profile groupsBoot 2.4+ — gom nhiều profile thành 1 logic tên: activate 1 tên, Boot include cả nhómBài 05
SLF4JSimple Logging Facade for Java — facade interface, code app chỉ dùng API này, không bind implementationBài 06
MDC (Mapped Diagnostic Context)ThreadLocal map cho log — set correlation ID per-request, extract trong log pattern %X{key}Bài 06
ECS (Elastic Common Schema)Schema JSON chuẩn cho Elasticsearch — Boot 3.4 GA support native qua logging.structured.format.console: ecsBài 06

⚠️ Pitfall tổng hợp

10 pitfall lớn nhất gom từ section "Nhầm" và "Pitfall" của các bài. Mỗi pitfall: code sai → code đúng + lý do.

1. Khai báo version explicit cho dep Spring trong Boot project

<!-- SAI: override BOM, gay mismatch toan ecosystem -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.0</version>
</dependency>

<!-- DUNG: de BOM resolve -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
</dependency>

Lý do: BOM spring-boot-dependencies đã set version tương thích. Override 1 lib → version lệch với các lib còn lại trong ecosystem → runtime bug tinh tế hoặc compile fail.

2. Thiếu @ConditionalOnMissingBean trong autoconfig

// SAI: user register cung type -> conflict
@AutoConfiguration
public class MyConfig {
    @Bean
    public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); }
}

// DUNG: rut lui khi user override
@AutoConfiguration
public class MyConfig {
    @Bean
    @ConditionalOnMissingBean
    public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); }
}

Lý do: mọi @Bean trong autoconfig là "default" — phải rút lui khi user đăng ký bean cùng type. Thiếu → NoUniqueBeanDefinitionException khi user customize.

3. @ConditionalOnClass(value = ThirdPartyClass.class) trong autoconfig

// SAI: compile fail khi ThirdPartyClass khong co classpath
@AutoConfiguration
@ConditionalOnClass(KafkaTemplate.class)
public class MyKafkaConfig { ... }

// DUNG: string, khong require import
@AutoConfiguration
@ConditionalOnClass(name = "org.springframework.kafka.core.KafkaTemplate")
public class MyKafkaConfig { ... }

Lý do: autoconfig phải compile được kể cả khi class điều kiện không có classpath. Dùng name string tránh compile dependency.

4. Hardcode secret trong application.yml

# SAI: commit git -> leak secret
spring:
  datasource:
    password: prod-secret-2026

# DUNG: reference env var
spring:
  datasource:
    password: ${DB_PASSWORD}

Lý do: file application.yml commit git → secret lộ. Repository scanner (GitGuardian, GitHub Secret Scanning) alert. Secret luôn qua env var / K8s Secret / Vault.

5. Thiếu @Validated trên @ConfigurationProperties

// SAI: field null -> NPE runtime sau khi app da start
@ConfigurationProperties(prefix = "app")
public record AppProps(String dbUrl) {}

// DUNG: fail-fast tai startup
@ConfigurationProperties(prefix = "app")
@Validated
public record AppProps(@NotBlank String dbUrl) {}

Lý do: thiếu validation → user quên set property → field null → NPE xảy ra runtime khi có request thay vì tại startup. Fail-fast luôn tốt hơn fail-late.

6. Quên @ConfigurationPropertiesScan hoặc @EnableConfigurationProperties

// SAI: record khong tu register
@ConfigurationProperties(prefix = "app")
public record AppProps(String name) {}

// DUNG: scan tu dong
@SpringBootApplication
@ConfigurationPropertiesScan
public class App { ... }

Lý do: @ConfigurationProperties không tự register thành bean. Cần scan hoặc explicit enable. Quên → inject AppProps fail với NoSuchBeanDefinitionException.

7. Profile name typo — app vẫn start

# SAI: typo 'prdo' -- app start voi default profile, bug ngam
java -jar app.jar --spring.profiles.active=prdo

# DUNG: validate startup
# @EventListener(ApplicationReadyEvent.class) check env.getActiveProfiles()

Lý do: Boot không validate tên profile — typo → 0 profile active → fallback default config → bug ngầm không có error message rõ ràng. Cần validation startup explicit.

8. Bean @Profile("prod") inject vào service không có @Profile

// SAI: NoSuchBeanDefinitionException khi dev profile active
@Component @Profile("prod")
public class CloudWatchExporter implements MetricsExporter { ... }

@Service
public class CommonService {
    @Autowired MetricsExporter exporter;   // null khi profile=dev
}

// DUNG: interface + 2 implementation
@Component @Profile("prod")
public class CloudWatchExporter implements MetricsExporter { ... }

@Component @Profile("!prod")
public class ConsoleExporter implements MetricsExporter { ... }

@Service
public class CommonService {
    @Autowired MetricsExporter exporter;   // moi profile co implementation
}

Lý do: service không có @Profile register ở mọi profile, nhưng dep của nó thiếu ở profile dev. Luôn cung cấp implementation cho mọi profile qua interface.

9. Log exception sai cách — mất stack trace

// SAI: chi log message, mat stack trace
log.error("Payment failed: " + e.getMessage());
log.error("Payment failed: {}", e);

// DUNG: exception la param cuoi
log.error("Payment failed for order {}", orderId, e);

Lý do: SLF4J detect param cuối là Throwable → log message + full stack trace. Thiếu stack trace = debug production mù.

10. Quên MDC.clear() trong filter — correlation ID leak

// SAI: exception trong chain -> MDC khong clear -> leak
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
    MDC.put("requestId", id);
    chain.doFilter(req, res);
    MDC.clear();           // KHONG chay khi chain throw exception
}

// DUNG: try-finally dam bao clear
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
    MDC.put("requestId", id);
    try {
        chain.doFilter(req, res);
    } finally {
        MDC.clear();       // luon chay
    }
}

Lý do: Tomcat reuse thread — không clear MDC → thread T1 xử lý request A lỗi rồi xử lý request B với requestId của A → log corrupt correlation ID.

✅ Self-assessment outcomes

Tick được hết các ô sau, bạn sẵn sàng Module 03. Nếu chưa: re-read bài tương ứng trước khi tiếp tục.

  • Explain 5 trụ cột Spring Boot và vì sao Boot ra đời năm 2014 để xoá boilerplate Spring 4.
    • Nếu chưa: re-read bài 01 section 1-3 ("Bài toán Spring 4", "5 trụ cột", "Opinionated vs lock-in") + SelfCheck Q1.
  • Trace chuỗi sự kiện từ classpath đến bean khi thêm 1 starter — bóc AutoConfigurationImportSelector, file AutoConfiguration.imports, và 12 @ConditionalOn* family.
    • Nếu chưa: re-read bài 03 section 2-4 ("entry point", "file AutoConfiguration.imports", "family @ConditionalOn*") + SelfCheck Q1.
  • Diagnose tại sao 1 autoconfig bean không được tạo bằng /actuator/conditions trong dưới 5 phút.
    • Nếu chưa: re-read bài 03 section 6 ("Debug autoconfig — Actuator /conditions") + SelfCheck Q3. Bật endpoint, curl /actuator/conditions, search bean bị miss.
  • Implement type-safe configuration binding với @ConfigurationProperties record + @Validated + relax binding cho K8s env var.
    • Nếu chưa: re-read bài 04 section 5.2-5.3 (@ConfigurationProperties + records) + section 6 (relax binding) + SelfCheck Q6. Tự viết 1 record config và test bind từ env var.
  • Choose đúng profile layout (single file vs group vs multi-document YAML) cho multi-environment deployment với DRY.
    • Nếu chưa: re-read bài 05 section 4-7 (groups, multi-document, inheritance) + SelfCheck Q4. Thiết kế layout 3-region từ scratch không nhìn tài liệu.
  • Design logging stack cho production: structured JSON (Boot 3.4 ECS) + MDC correlation ID + dynamic log level qua Actuator mà không restart app.
    • Nếu chưa: re-read bài 06 section 5-6 (structured logging + Actuator /loggers) + làm lại mini-challenge bài 07 phần "Mức 3-4". Bật logging.structured.format.console: ecs và verify JSON output.

🚀 What's next — Module 03: Spring MVC & Web Layer

Module 02 đã bóc tách nền tảng Boot — 5 trụ cột, auto-config, config, profile, logging. Module 03 bước vào Web Layer: Spring MVC architecture, DispatcherServlet routing, @RestController đầy đủ, validation với Jakarta Validation, exception handling chuẩn, và OpenAPI/Swagger documentation. Bạn sẽ build REST API CRUD đầu tiên cho domain TaskFlow — capstone xuyên suốt khoá.

Specifically, sau Module 03 bạn sẽ giải đáp: DispatcherServlet route request qua bao nhiêu layer trước khi đến @GetMapping method? Vì sao @Valid trên @RequestBody không hoạt động khi thiếu 1 annotation? @RestControllerAdvice khác @ControllerAdvice ở chỗ nào và khi nào dùng cái nào?

→ Đi tới Module 03: Spring MVC & Web Layer

📚 Tài liệu mở rộng

Sách:

  • Spring Boot in Practice — Somnath Musib (Manning, 2022). Chương 1-3 cover Boot foundations với nhiều ví dụ thực tế.
  • Learning Spring Boot 3 — Greg L. Turnquist (Packt, 2023). Mỗi chapter 1 project nhỏ — học qua làm.

Paper / RFC / JEP:

Video / Talk:

Source code để đọc:

Blog series:

  • Baeldung Spring Boot — 200+ bài cover từng annotation/pattern. Lookup khi gặp vấn đề cụ thể.
  • Spring Blog — release notes, feature announcement từ Pivotal team. Theo dõi Boot 3.x changelog.

Chúc mừng — bạn đã hoàn thành Module 02. Bạn hiểu Spring Boot không phải magic — là Java code với pattern @ConditionalOn* lặp đi lặp lại có thể đọc và trace được. Mọi auto-configuration đều có lý do, mọi property đều có source, mọi log đều có correlation ID. Nghỉ 1 ngày cho concept lắng xuống, rồi vào Module 03 — Web Layer.

Bài này có giúp bạn hiểu bản chất không?