Spring Core Concepts

本文整合 Spring Framework 的核心概念,依學習順序編排。


Spring 與 JakartaEE

Spring 整合了多項 JakartaEE (J2EE) 標準:

  1. Servlet API (JSR 340)
  2. WebSocket API (JSR 356)
  3. Concurrency Utilities (JSR 236)
  4. JSON Binding API (JSR 367)
  5. Bean Validation (JSR 303)
  6. JPA (JSR 338)
  7. JMS (JSR 914)
  8. 支援 Dependency Injection (JSR 330)Common Annotations (JSR 250)

Spring IoC (控制反轉)

Inversion of Control: 指的是將物件的建立與依賴管理權限,從開發者手中轉交給 Spring 容器。

控制反轉:一種設計思想,將物件的控制權轉移給第三方容器(IoC 容器)。

IoC 容器類型

  • BeanFactory: 基礎介面,提供進階配置機制。
  • ApplicationContext: BeanFactory 的子介面,增加 AOP 整合、訊息處理、事件發布等企業級功能。

Spring DI (依賴注入)

Dependency Injection: 實現 IoC 的一種具體技術。

  • 物件不再自行建立依賴,而是由容器主動注入(控制反轉)。
  • 優勢: 易於測試(注入 Mock 物件)、低耦合、配置靈活。
依賴注入:將相依的物件(由容器建立)注入到需要它的物件中。

注入方式

  • Setter 注入: 透過 setXXX() 方法。
  • Constructor 注入: 透過建構子。
  • 自動裝配 (Autowire):
    • byType: 根據屬性類型尋找。
    • byName: 根據屬性名稱(Bean ID)尋找。

Java EE 6 CDI vs Spring DI

功能Java EE 6 CDISpring DI
標準化Java 標準 API (CDI 規範)。第三方框架。
註解註解數量較少,系統較單純。註解種類極多,功能強大但較複雜。
配置較僵硬但易於理解。靈活的 XML/Java 配置。

Spring Bean

  • 由 Spring IoC 容器管理的所有 Java 物件。

Bean Scope (作用域)

  • Singleton: 全域單例。
  • Prototype: 每次請求建立新實例。
  • Web 專用: Request, Session, Application, WebSocket

Spring Bean Lifecycle (生命週期)

四個主要階段:

  1. 實例化 (Instantiation)
  2. 屬性填充 (Populate)
  3. 初始化 (Initialization)
  4. 銷毀 (Destruction)

簡易流程圖

  flowchart LR
	c[Constructor]
	a[Autowired]
	p[PostConstruct]
	i[InitializingBean]
	app[ApplicationRunner]
	cli[CommandLineRunner]
	c --> a --> p --> i --> app --> cli

詳細流程圖

  flowchart TB
    subgraph Instantiation_實例化
    	Constructor
    end
    subgraph Population_Of_Properties_屬性填充
    	Setter_Injection
    end
    subgraph BeanNameAware_設定Bean名稱
		setBeanName
    end
	subgraph setBeanFactory_設定工廠
		BeanFactoryAware
    end
	subgraph ApplicationContext_設定上下文
		setApplicationContext
    end
	subgraph BeanPostProcessor_預處理
		postProcessBeforeInitalization
    end
	subgraph PostConstruct_註解初始
		init_method
    end
	subgraph InitializingBean_介面初始
		afterPropertiesSet
    end
	subgraph Custom_Initialization_自定義初始
		custom_init_method
    end
	subgraph postProcess_AfterIntialization_後處理
		p_BeanPostProcessor
    end
	subgraph PreDestroy_預銷毀
		destroy_method
    end
	subgraph DisposableBean_介面銷毀
		destroy_method
    end
	subgraph Custom_Destruction_自定義銷毀
		Custom_Destroy_Method
	end

	Instantiation_實例化 ==> Population_Of_Properties_屬性填充 ==> BeanNameAware_設定Bean名稱
	BeanNameAware_設定Bean名稱 ==> setBeanFactory_設定工廠 ==> ApplicationContext_設定上下文
	ApplicationContext_設定上下文 ==> BeanPostProcessor_預處理 ==> PostConstruct_註解初始 ==> InitializingBean_介面初始
	InitializingBean_介面初始 ==> Custom_Initialization_自定義初始 ==> postProcess_AfterIntialization_後處理
	postProcess_AfterIntialization_後處理 ==> PreDestroy_預銷毀 ==> DisposableBean_介面銷毀
	DisposableBean_介面銷毀 ==> Custom_Destruction_自定義銷毀

ApplicationContextAware 介面

實作此介面的 Bean 可以在啟動時獲取 ApplicationContext 的參照,用於手動獲取容器中的其他 Bean。

  • Use Case (使用場景)
    • 在事件監聯器中,當需要根據業務邏輯動態獲取 Bean 時 (例如避免循環依賴)。
    • 在工具類別 (Utility/Helper Class) 中,透過靜態方法訪問 Spring 管理的服務。
@Component
public class SpringContextHolder implements ApplicationContextAware {
	private static ApplicationContext appContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		appContext = applicationContext;
	}

	public static ApplicationContext getApplicationContext() {
		checkApplicationContext();
		return appContext;
	}

	@SuppressWarnings("unchecked")
	public static <T> T getBean(String name) {
		checkApplicationContext();
		return (T) appContext.getBean(name);
	}

	@SuppressWarnings("unchecked")
	public static <T> T getBean(Class<T> clazz) {
		checkApplicationContext();
		Map beanMaps = appContext.getBeansOfType(clazz);
		if (beanMaps != null && !beanMaps.isEmpty()) {
			return (T) beanMaps.values().iterator().next();
		} else {
			return null;
		}
	}

	private static void checkApplicationContext() {
		if (appContext == null) {
			throw new IllegalStateException("applicationContext has not been injected yet.");
		}
	}
}

Spring AOP (切面導向)

透過代理模式實現功能的橫向切入(如日誌、安全性、事務),將業務邏輯與基礎設施關注點分離。

  flowchart LR
	0((AOP)) --> 1([Around 前置處理]) --> 2([Before]) --> 3[Target Object] --> 4([AfterReturning]) --> 5([After]) --> 6([Around 後置處理])

CGLIB 代理

Code Generation Library

CGLIB 是一個高效能的代碼生成庫。在 Spring AOP 中,當目標物件 沒有實作介面 時,Spring 會自動使用 CGLIB 建立代理物件。它透過繼承目標類別來實作代理,因此無法代理 final 類別或方法。


Transaction (事務管理)

Propagation (傳播行為)

@Transactional(propagation = Propagation.REQUIRED)

參數描述
REQUIRED預設值。如果當前存在事務,則加入;否則建立一個新事務。
SUPPORTS如果當前存在事務,則加入;否則以非事務方式執行。
MANDATORY必須在事務中執行,否則拋出異常。
REQUIRES_NEW掛起當前事務,建立一個全新的獨立事務。
NOT_SUPPORTED以非事務方式執行,如果當前有事務則先掛起。
NEVER禁止在事務中執行,否則拋出異常。
NESTED如果當前有事務,則在嵌套事務中執行(子事務回滾不影響父事務,但父事務回滾會影響子事務)。

行為對比範例

  • REQUIRED: A 方法調用 B 方法,兩者處於同一事務。B 出錯,全體回滾。
  • REQUIRES_NEW: A 方法調用 B 方法,B 開啟獨立事務。B 回滾不影響 A;A 回滾不影響已提交的 B。
  • NESTED: A 方法調用 B 方法,B 是 A 的子事務。B 出錯可單獨回滾(需 Catch),A 可繼續執行。

應用場景 (Use Case)

  1. 獨立日誌記錄: 業務失敗也需要記錄日誌,此時日誌服務應設為 REQUIRES_NEW
  2. 複雜業務補償: 訂單主流程與積分贈送,若積分失敗不應導致訂單失敗,可使用 NESTED 配合 Try-Catch。
@Transactional(propagation = Propagation.REQUIRED)
public void mainProcess() {
    orderService.saveOrder(); // 成功
    try {
        logService.log(); // 設為 REQUIRES_NEW,即使 A 回滾,日誌依然存在
    } catch (Exception e) {}
}

WebSocket API

  flowchart TB
	HTTP握手攔截器[HttpSessionHandshakeInterceptor]
	HTTP握手前[beforeHandshake]
	HTTP握手後[afterHandshake]
	WssText處理器[TextWebScoketHandler]
	是否支持內容拆分[supportPartialMessages]
	Wss連線建立後[afterConnectionEstablished]
	Wss連線關閉後[afterConnectionClosed]

	HTTP握手攔截器 --> HTTP握手前 --> HTTP握手後 --> WssText處理器 --> 是否支持內容拆分 --> Wss連線建立後 --> Wss連線關閉後