Spring Boot Overview
Spring Boot 透過「設定優先於配置」(Convention over Configuration) 及其預設的 Starter 依賴,簡化了新 Spring 應用程式的開發。
Spring Boot 3.x
Three Stages
flowchart TB
S[listeners.starting 啟動開始]
pEnv[prepareEnvironment 準備環境]
pCtx[prepareContext 準備上下文]
rCtx[refreshContext 刷新上下文]
s[startup.started 啟動完成]
r[ready App 準備就緒]
F[failed 失敗]
S ===>|準備環境變數| pEnv ===>|準備並設定上下文| pCtx ===>|刷新 Ctx|rCtx ====>|Context 啟動完成|s ===>|執行 Runners| r
pEnv ---->|Error|F
pCtx ---->|Error|F
rCtx ---->|Error|F
s ---->|Error|F
r ---->|Error|F
Prepared (準備階段)
I. Constructor
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//...
}- 判定 Application 類型
this.webApplicationType = WebApplicationType.deduceFromClasspath();使用
class SpringFactoriesLoader(SPI 機制) 載入(META-INF/spring.factories) Key所有可實例化之初始器與監聽器 (SpringApplicationRunListener 的子類別)設定所有 Application 事件監聽器
this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));- 從 StackFrame 中找出 App Main Class
this.mainApplicationClass = deduceMainApplicationClass();II. Spring Application Instance.run
public ConfigurableApplicationContext run(String... args) {
//...
try {
//...
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, listeners);
}
try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, null);
}
return context;
}Startup startup = Startup.create(): 記錄啟動效能與追蹤。DefaultBootstrapContext bootstrapContext = createBootstrapContext();- 建立一個「引導上下文」,用於在初始階段緩存、共享或傳遞程式碼中所需的物件實例,供上下文關聯與設定。
- 在正式
Spring Context建立前用來共享物件。
configureHeadlessProperty();: java.awt.headless 設定,default=true- 獲取所有註冊於
SpringApplicationRunListener的監聽器 (listeners)。 - 發布 ApplicationStartingEvent,通知所有相關元件 (觀察者模式 Observer Pattern)。
- 獲取所有註冊於
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);- 處理命令列參數。
- 準備執行時的環境變數。
- 印出 Banner。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = printBanner(environment);Context Created (上下文建立)
- 根據 Web App 類型建立 Application Context。
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}- 設定啟動過程的記錄器。
public void setApplicationStartup(ApplicationStartup applicationStartup) {
this.applicationStartup = (applicationStartup != null) ? applicationStartup : ApplicationStartup.DEFAULT;
}- 準備 Context
- postProcessApplicationContext() : 注入環境屬性 (properties)。
- applyInitializers() : 執行在 Prepared 階段獲取的所有初始器 (ApplicationContextInitializer)。
- listeners.contextPrepared(context) : 通知上下文準備完成。
- bootstrapContext.close(context) : 關閉引導上下文。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) Refreshed Context (刷新上下文)
連結: SpringApplicationShutdownHook
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
refresh(context);
}After Refreshed (刷新後處理)
afterRefresh(context, applicationArguments);- 停止
Startup.create()的啟動計時。
載入並執行 ApplicationRunner, CommandLineRunner
private void callRunners(ConfigurableApplicationContext context, ApplicationArguments args) 啟動失敗處理
private RuntimeException handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
SpringApplicationRunListeners listeners) 啟動時可擴充的擴充介面
參考: 這些 16 個 SpringBoot 擴充介面讓程式碼更優雅
- Spring 容器內 Bean 的生命週期
flowchart TB
ACIinit[ApplicationContextInitializer: void initialize]
AACrf[AbstractApplicationContext: + void refresh]
BDRPP-pPBDR[BeanDefinitionRegistryPostProcessor: void postProcessBeanDefinitionRegistry]
BFPP[BeanFactoryPostProcessor: postProcessBeanFactory]
IABPP-pPBI[InstantiationAwareBeanPostProcessor: postProcessBeforeInstantiation]
SIABPP-dCC[SmartInstantiationAwareBeanPostProcessor: determineCandidateConstructors]
MBDPP-pPMBD[MergedBeanDefinitionPostProcessor: postProcessMergedBeanDefinition]
IABPP-pPAI[InstantiationAwareBeanPostProcessor: postProcessAfterInstantiation]
SIABPP-gEBR[SmartInstantiationAwareBeanPostProcessor: getEarlyBeanReference]
BFA-sBF[BeanFactoryAware: setBeanFactory]
IABPP-pPPVs[InstantiationAwareBeanPostProcessor: postProcessProperties]
ACAP-iAI[ApplicationContextAwareProcessor: invokeAwareInterfaces]
BNA-sBN[BeanNameAware: setBeanName]
IABPP-pPBInit[InstantiationAwareBeanPostProcessor: postProcessBeforeInitialization]
PC[@PostConstruct]
InitBean-aPpS[InitializingBean: afterPropertiesSet]
IABPP-pPAInit[InstantiationAwareBeanPostProcessor: postProcessAfterInitialization]
FB-gO[FactoryBean: getObject]
SIS-aSI[SmartInitializingSingleton: afterSingletonsInstantiated]
CLR-run[CommandLineRunner: run]
ACIinit ---> AACrf ---> BDRPP-pPBDR ---> BFPP ---> IABPP-pPBI ---> SIABPP-dCC ---> MBDPP-pPMBD ---> IABPP-pPAI ---> SIABPP-gEBR ---> BFA-sBF ---> IABPP-pPPVs ---> ACAP-iAI ---> BNA-sBN ---> IABPP-pPBInit ---> PC ---> InitBean-aPpS ---> IABPP-pPAInit ---> FB-gO ---> SIS-aSI ---> CLR-run
- ApplicationContextInitializer
用於在 Spring Context refresh 之前進行設定的擴充介面。
通常用於在 SpringBoot 容器中調整 Properties
或是修改 ConfigurableApplicationContext。
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}Spring Bean Lifecycle
flowchart TB c[Constructor] a[Autowired] p[PostConstruct] i[InitializingBean] app[ApplicationRunner] clr[CommandLineRunner] c --> a --> p --> i --> app --> clr
@PostConstruct與@PreDestroy註解都是 Java EE 的一部分。- 由於 Java EE 在 Java 9 中被棄用,並在 Java 11 中移除。
- Spring 替代方案:
InitializingBean, DisposableBean
class & interface 的深度剖析
SpringApplicationShutdownHook
連結: Threads Related, AbstractApplicationContext
AbstractApplicationContext
負責在 destroyBeans() 之前關閉線程池與上下文資源。
- 發布
class ContextClosedEvent事件,在事件監聽中優雅關閉線程池。 - 調用
interface Lifecycle-stop()方法。
protected void doClose() {
// 檢查是否需要執行關閉操作...
if (this.active.get() && this.closed.compareAndSet(false, true)) {
//...
try {
// 發布關閉事件
publishEvent(new ContextClosedEvent(this)); // 步驟 1
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// 停止所有 Lifecycle beans,避免個別銷毀時的延遲。
if (this.lifecycleProcessor != null) {
try {
this.lifecycleProcessor.onClose(); // 步驟 2
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
}
// 銷毀容器內緩存的所有單例 Beans。
destroyBeans(); // 在銷毀 Beans 之前
//...
// 切換為不活躍狀態。
this.active.set(false);
}
}ThreadPoolTaskExecutor
Spring 封裝此類別用於管理線程池。 在需要優雅關閉時,可以調用
destroy()方法或是配置其生命週期。
@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
threadPoolTaskExecutor.destroy();
}
// ...
}ExecutorConfigurationSupport
public void setWaitForTasksToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
this.waitForTasksToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
}
public void setAwaitTerminationSeconds(int awaitTerminationSeconds) {
this.awaitTerminationMillis = awaitTerminationSeconds * 1000L;
}AbandonedRunException
Spring Boot 啟動 AOT 模式或是啟動時被異常中斷拋出的異常。
SpringBootExceptionReporter
- FailureAnalyzers
獲取啟動失敗後的 FailureAnalysis 解析。
- FailureAnalysisReporter
格式化列印失敗解析訊息。
Startup Steps
Events
| 階段 | Event | Spring Boot 2 | Spring Boot 3 | 說明 |
|---|---|---|---|---|
| 1 | ApplicationStartingEvent | ✅ | ✅ | 應用程式啟動開始 |
| 2 | ApplicationEnvironmentPreparedEvent | ✅ | ✅ | Environment 環境準備完成 |
| 3 | ApplicationContextInitializedEvent | ✅ | ✅ | ApplicationContext 初始完成 |
| 4 | ApplicationPreparedEvent | ✅ | ✅ | Bean 定義載入完成 |
| 5 | ContextRefreshedEvent | ✅ | ✅ | ApplicationContext 刷新完成 |
| 6 | ApplicationStartedEvent | ✅ | ✅ | 啟動完成並準備執行 Runner |
| 7 | AvailabilityChangeEvent | ✅ | ✅ | 應用程式可用狀態變更 (ACCEPTING_TRAFFIC) |
| 8 | ApplicationReadyEvent | ✅ | ✅ | 完全就緒,可以處理請求 |
| - | ApplicationFailedEvent | ✅ | ✅ | 啟動失敗時觸發 |
Spring Framework vs Spring Boot
| 特性 | Spring Framework | Spring Boot |
|---|---|---|
| 定位 | 全能的企業級框架。 | 基於 Spring 的「快速開發」工具。 |
| 配置 | 需手動撰寫大量 XML 或 Java Config。 | 自動配置 (Auto-configuration)。 |
| 部署 | 需外部 Servlet 容器 (如 Tomcat)。 | 內嵌容器,可獨立執行 (Fat JAR)。 |
| 依賴 | 手動管理 Lib 版本。 | Starter 依賴簡化版本管理。 |
DTO Pattern (資料傳輸物件模式)
用於在不同層級(如 Controller 與 Service)之間傳遞資料,避免直接暴露資料庫實體(Entity)。
MultipartFile vs File
| 類型 | 說明 |
|---|---|
| MultipartFile | Spring 特有介面,代表前端上傳的文件流。 |
| File | Java 標準類別,代表檔案系統中的實體路徑。通常需要將前者轉換為後者以進行存儲。 |
Glossary (術語表)
A
Actuator
Micrometer
Micrometer 作為各種監控系統的門面, 提供了一種與工具無關的介面來收集指標, 並將指標發布到我們的目標指標收集器。
flowchart LR App[Application] M[Micrometer] subgraph Monitoring-System At[Atlas] CW[CloudWatch] Dg[Datagog] Pmt[Prometheus] end App --> M --> Monitoring-System
MeterRegistry & Meter
classDiagram class MeterRegistry class CloudWatchMeterRegistry class PrometheusMeterRegistry class Meter <<interface>> Meter MeterRegistry ..> Meter CloudWatchMeterRegistry --|> MeterRegistry PrometheusMeterRegistry --|> MeterRegistry
- Timer
用於測量短時間延遲以及此類事件的頻率。所有
Timer的實作至少將總時間和事件計數作為獨立的時間序列報告。 - Counter 用於測量僅增加的數值。它們可用於計算服務的請求數、完成的任務數、發生的錯誤等。
- Gauge 表示一個既可以增加也可以減少的數值。Gauge 用於測量目前的 CPU 使用率、緩存大小、隊列中的消息數等值。
- DistributionSummary
- LongTaskTimer
- FunctionCounter
- FunctionTimer
- TimeGauge
Auto Configuration
exclude (排除自動配置)
@SpringBootApplication(exclude = {
RedisAutoConfiguration.class,
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
})B
C
ClassPathResource
- 靜態載入 Resource 資料夾中的資源
ClassPathResource resource = new ClassPathResource(file_location);
try (InputStream is = resource.getInputStream();
InputStreamReader reader = new InputStreamReader(is)) {
// ...
}D
Deploy Externally Tomcat
- 修改
pom.xml
<packaging>war</packaging>- 加入依賴 / 排除依賴 (擇一)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>public abstract class SpringBootServletInitializer implements WebApplicationInitializer- 方式一
@SpringBootApplication
public class RSApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(RSApplication.class, args);
}
}- 方式二
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
// Application.class 是標有 @SpringBootApplication 註解的啟動類別
return builder.sources(Application.class);
}
}E
Embedded Tomcat
連結:
- Java Keytool
- Spring Profiles
Redirect HTTP To HTTPS
No Spring Security
Way 1
- Use Both (同時支援)
Way 2
- HTTP to HTTPS (強制跳轉)
Way 3
- 指定路徑從 HTTP 跳轉至 HTTPS
- 使用 Interceptor
F
G
GraalVM
- GraalVM CLI
- GraalVM Glossary
Native image
是 GraalVM 中的一種技術,它可以將應用程式與其依賴的 Jar 檔預編譯成一個單獨的可執行二進制檔案。 此二進制檔案已針對特定的平台進行優化,不再依賴 Java 虛擬機運行。
與 JVM 部署的關鍵差異
- 應用程式的類別路徑 (classpath) 在建置時就已固定,無法更改。
- 沒有延遲類別載入 (lazy class loading),所有編入執行檔的內容都會在啟動時載入記憶體。
Spring AOT Processing
Restrictions (限制)
- 類別路徑在建置時固定且完全定義。
- 定義於應用程式中的 Beans 不能在運行時更改:
- 不支援 Spring @Profile 註解及其特定設定。
- 不支援動態創建 Bean 的屬性 (例如 @ConditionalOnProperty)。
- 不支援
@Profile註解與特定屬性配置。
處理程序將會產生
- Java 源代碼
- 字節碼 (用於動態代理等)
- GraalVM JSON 提示檔案:
- 資源提示 (
resource-config.json) - 反射提示 (
reflect-config.json) - 序列化提示 (
serialization-config.json) - Java 代理提示 (
proxy-config.json) - JNI 提示 (
jni-config.json)
- 資源提示 (
H
I
J
Jackson
ObjectMapper
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS: 序列化時日期是否使用時間戳記;預設為 true (使用時間戳記);設定為 false 可以將日期格式化為字串。SerializationFeature.FAIL_ON_EMPTY_BEANS: 序列化空物件時是否拋出異常;預設為 true。SerializationFeature.INDENT_OUTPUT: 是否格式化輸出;預設為 false。SerializationFeature.WRITE_NULL_MAP_VALUES: 是否序列化為 null 的鍵值對;預設為 true。DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES: 反序列化時遇到未知屬性時,是否拋出異常;預設為 true。DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL: 是否將未知列舉值反序列化為 null;預設為 false。DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT: 是否將空字串反序列化為 null;預設為 false。JsonParser.Feature.ALLOW_COMMENTS: 是否允許 JSON 中帶有註解;預設為 false。JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES: 是否允許 JSON 中的欄位名稱不使用雙引號;預設為 false。setSerializationInclusionJsonInclude.Include.NON_DEFAULT屬性為預設值時不序列化。JsonInclude.Include.ALWAYS始終序列化。JsonInclude.Include.NON_EMPTY屬性為 空(或 “”) 或為NULL時不序列化。JsonInclude.Include.NON_NULL屬性為 NULL 時不序列化。
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);readValue()
mapper.readValue(reader, new TypeReference<T>() {});- Enable Pretty Print Json
DefaultPrettyPrinter dpp = new DefaultPrettyPrinter();
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(data);issues
Java 8 date/time type java.time.LocalDateTime not supported by default
java.time.LocalDateTime not supported by default: add Module “com.fasterxml.jackson.datatype:jackson-datatype-jsr310” to enable handling (through reference chain: net.arplanets.nmns.model.raw.RawNmnsData[“dataDate”])- 解決方案
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.4</version>
</dependency>ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule())K
L
M
MultivaluedMap
- 鍵值對 Map。
- 每個鍵可以擁有 零個或多個值。
Multi-threading
@EanbleAsync=> 配置類別@Async=> 方法
N
Executable JARs
O
P
Q
R
Reactive Web
S
Scheduling Tasks
Cron 表達式語法
e.g. 秒 分 時 日 月 星期 年- 每天 0:00 執行一次定時任務
@Scheduled(cron = "0 0 0 * * ?")
public void testCron() {
logger.info("===cron: 時間:{}", dateFormat.format(new Date()));
}- 每星期一 0:00 執行一次定時任務
@Scheduled(cron = "0 0 0 ? * MON")
public void testCron() {
logger.info("===cron: 時間:{}", dateFormat.format(new Date()));
}- 每月1號 0:00 執行一次定時任務
@Scheduled(cron = "0 0 0 1 * ?")
public void testCron() {
logger.info("===cron: 時間:{}", dateFormat.format(new Date()));
}T
Testing
- Testing Glossary
- JUnit & Mockito Annotations
Scope Dependencies
JUnit 5
JUnit Platform + JUnit Jupiter + JUnit Vintage- Vintage
- 支援在
JUnit 5平台上執行基於JUnit 3與JUnit 4的測試。
- 支援在
flowchart BT subgraph TOP YT[Your Test] end subgraph junit5 jja[junit-jupiter-api] jje[junit-jupiter-engine] end subgraph junit4 j4[junit-4.12] jve[junit-vintage-engine] end subgraph totally_made-up_example ta[testing-api] te[testing-engine] end jpe[junit-platform-engine] jpl[junit-platform-launcher] IDE[Intellij IDEA, Eclipse, Apache, Gradle, etc.] YT ---->|Written against| jja YT ----> j4 YT ----> ta jve ----> j4 jje ----> jja te ----> ta jve ----> jpe jje ---->|implements| jpe te ----> jpe jpl ---->|Discovers implements via ServiceLoader, Orchestrates execution| jpe IDE ---->|Use exclusively| jpl
JUnit 4
Spring Test
DataJpaTest
- 使用嵌入式記憶體資料庫
(H2)。 - 自動配置 Transaction, Auto Rollback。
- 若想提交至 in-memory database, 標註
@Commit。
- 若想提交至 in-memory database, 標註
WebMvcTest
MockMvcRequestBuildersMockMvcResultHandlers
單元測試中是否包含以下項目?
- 監聽 HTTP 請求? -> 否,因為單元測試不會評估
@PostMapping及其相關屬性。 - 反序列化輸入? -> 否,因為
@RequestParam與@PathVariable等註解不會被評估。 - 驗證輸入? -> 否,如果不依賴 Bean Validation,
@Valid註解將不被評估。 - 調用業務邏輯? -> 是,我們可以驗證 Mock 的業務邏輯是否以預期的參數被調用。
- 序列化輸出? -> 否,因為我們只能驗證輸出的 Java 物件,而非生成 HTTP 回應。
- 轉換異常? -> 我們可以檢查是否拋出了特定異常,但無法確認它是否被轉換為特定 JSON 或 HTTP 狀態碼。
AssertJ
A fluent assertion library(流暢的斷言庫)。
Hamcrest
Mockito
JSONassert
JsonPath
Version Naming Scheme (版本命名慣例)
BUILD-SNAPSHOT: 目前開發中的快照版本。M<number>: Milestone (里程碑) 版本,表示發布過程中的重大階段。RELEASE: 正式發布版。GA: General Availability,正式發布穩定商用版,代表該版本經過廣泛測試並建議在生產環境中使用。SNAPSHOT: 開發版,可以隨時被修改,不建議在生產環境中使用。RC<number>:- Release Candidate (發布候選版),通常是正式發布前的最後一步。
Umbrella Projects (雨傘專案)
如 Spring Cloud 與 Spring Data,由多個獨立但相關的子專案組成。 每個 Release Train (發布列車) 都有一個特殊的名稱,而非單一版本號。