第三章:@SpringBootApplication 深度解析
3.1 前言:揭開@SpringBootApplication的神秘面纱
在上一章中,我們使用了 @SpringBootApplication 註解來標記主應用程式類。這個註解是 Spring Boot 的核心之一,但很多初學者只是機械地使用它,而不理解它背後的原理。在這一章節中,我們將深入探索 @SpringBootApplication 的組成和工作原理,讓你能夠真正理解 Spring Boot 的「魔法」。
理解這個註解不僅是為了滿足好奇心,更是為了成為一個優秀的 Spring Boot 開發者。當你遇到問題時,了解這些基礎概念可以幫助你更快地定位和解決問題。同時,這些知識也會讓你在閱讀官方文檔和源碼時更加得心應手。
3.2 @SpringBootApplication 的三個組成註解
@SpringBootApplication 實際上是一個組合註解(或稱為元註解),它由三個重要的註解組成。讓我們逐一分析每個組成部分。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
// 屬性配置
}
這段程式碼顯示 @SpringBootApplication 被標記了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 三個註解。這意味著當你使用 @SpringBootApplication 時,這三個註解的功能都會被啟用。
3.3 @Configuration:配置類的核心
@Configuration 是 Spring Framework 的核心註解之一,它標記一個類作為 Bean 定義的來源。讓我們詳細了解這個註解的作用和用法。
@Configuration 註解表明其標記的類是一個配置類,可以包含 @Bean 註解的方法。這些方法會被 Spring 容器調用,返回的對象會被註冊為 Spring 容器中的 Bean。
讓我們通過一個實際的範例來理解 @Configuration:
package com.example.myfirstapp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class AppConfig {
/**
* @Bean 註解標記的方法會被 Spring 容器調用,
* 返回的對象會被註冊為容器中的 Bean。
* 預設 Bean 的名稱就是方法名稱(這裡是 "restTemplate")。
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* 自定義 Bean 名稱
*/
@Bean(name = "customRestTemplate")
public RestTemplate customRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
// 自定義配置...
return restTemplate;
}
}
在這個範例中,AppConfig 類被標記為 @Configuration,這意味著 Spring 會將其視為配置類。類中的 restTemplate() 方法被 @Bean 註解標記,當 Spring 容器啟動時,它會調用這個方法並將返回的 RestTemplate 對象註冊為 Bean。
你可能會問:為什麼需要 @Configuration?為什麼不能直接使用 @Component?這是一個很好的問題。讓我們通過一個對比來理解:
// 使用 @Configuration
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService(myRepository());
}
@Bean
public MyRepository myRepository() {
return new MyRepository();
}
}
// 使用 @Component(不推薦)
@Component
public class AppConfig {
@Bean
public MyService myService() {
return new MyService(myRepository());
}
@Bean
public MyRepository myRepository() {
return new MyRepository();
}
}
這兩種寫法的關鍵區別在於:使用 @Configuration 時,Spring 會確保 Bean 之間的依賴關係被正確處理。如果你從同一個 @Configuration 類中的多個 @Bean 方法請求 Bean,Spring 會確保每次都返回同一個 Bean 實例(單例模式)。而使用 @Component 時,每次調用都會創建新的實例。
這是因為 @Configuration 類會被 CGLIB 代理,代理類會攔截所有 @Bean 方法的調用,確保它們返回的是容器中已存在的 Bean 實例,而不是每次都創建新的實例。
3.4 @EnableAutoConfiguration:自動配置的原理
@EnableAutoConfiguration 是 Spring Boot 特有的註解,它負責自動配置你的 Spring 應用程式。這個註解是 Spring Boot「約定優於配置」哲學的核心。
讓我們了解自動配置的工作原理。當 Spring Boot 啟動時,它會掃描類路徑中的各種條件,根據這些條件自動配置適合的 Bean。例如:
- 如果類路徑中包含
spring-boot-starter-web,自動配置會設置 Tomcat 內嵌伺服器 - 如果類路徑中包含
HibernateJpaAutoConfiguration,自動配置會設置 JPA 和 Hibernate - 如果類路徑中包含
DataSourceAutoConfiguration,自動配置會設置資料來源
這種自動配置的機制是通過 @Conditional 系列註解實現的。讓我們看看一些常用的條件註解:
// 條件:當類路徑中存在某個類時才配置
@ConditionalOnClass(name = "org.postgresql.Driver")
public class PostgreSqlDataSourceConfiguration {
// 配置邏輯
}
// 條件:當配置文件中存在某個屬性時才配置
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
public class DataSourceConfiguration {
// 配置邏輯
}
// 條件:當類路徑中缺少某個類時才配置
@ConditionalOnMissingClass("org.hibernate.Version")
public class AlternativeConfiguration {
// 配置邏輯
}
// 條件:當應用程式是 Web 應用程式時才配置
@ConditionalOnWebApplication
public class WebMvcConfiguration {
// 配置邏輯
}
你可以創建自己的自動配置類。以下是一個自定義自動配置的範例:
package com.example.myfirstapp.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@AutoConfiguration
@ConditionalOnClass(MyService.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService() {
return new MyService();
}
}
要讓 Spring Boot 識別你的自動配置,需要在 src/main/resources/META-INF/spring.factories 文件中註冊:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.myfirstapp.autoconfigure.MyAutoConfiguration
3.5 @ComponentScan:元件掃描的機制
@ComponentScan 負責掃描指定包及其子包中的類,將標記為 @Component、@Service、@Repository、@Controller 等註解的類註冊為 Spring 容器中的 Bean。
讓我們通過一個詳細的範例來理解元件掃描的工作原理:
package com.example.myfirstapp;
// 主應用程式類(Spring Boot 3.2.0+,預設掃描 com.example.myfirstapp 及其子包)
@SpringBootApplication
public class MyFirstAppApplication {
public static void main(String[] args) {
SpringApplication.run(MyFirstAppApplication.class, args);
}
}
在這個範例中,由於 @SpringBootApplication 包含 @ComponentScan,Spring 會自動掃描 com.example.myfirstapp 包及其所有子包下的類。
讓我們看看不同類型的元件:
// @Controller:表示這是一個 Web 控制器
package com.example.myfirstapp.controller;
@Controller
public class UserController {
// 處理 HTTP 請求
}
// @Service:表示這是一個服務類
package com.example.myfirstapp.service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
// @Repository:表示這是一個資料存取類
package com.example.myfirstapp.repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// JPA 自動實現
}
// @Component:通用元件
package com.example.myfirstapp.util;
@Component
public class DateUtils {
public String formatDate(LocalDateTime date) {
return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
Spring 會自動掃描這些類並將它們註冊為 Bean。在上面的範例中:
-
UserController會被註冊為 Bean,並且因為它標記了@Controller,它還會處理 HTTP 請求 -
UserService會被註冊為 Bean,Spring 會自動處理它的依賴注入 -
UserRepository會被註冊為 Bean,Spring Data JPA 會自動創建實現類 -
DateUtils會被註冊為 Bean,可以在其他地方注入使用
自定義元件掃描的範圍也是可以的:
// 自定義掃描範圍
@SpringBootApplication
@ComponentScan(
basePackages = {"com.example.myfirstapp", "com.example.common"},
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Test")
)
public class MyFirstAppApplication {
// ...
}
3.6 理解 Spring 容器和 Bean 生命週期
要真正理解 @SpringBootApplication 的運作方式,我們需要了解 Spring 容器和 Bean 的生命週期。
Spring 容器(也稱為 ApplicationContext)是 Spring Framework 的核心。它負責創建、配置和管理 Bean。以下是容器的主要職責:
- Bean 的實例化
- 依賴注入
- Bean 的配置
- Bean 的生命週期管理
Bean 的生命週期包含以下階段:
package com.example.myfirstapp.service;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
@Service
public class LifecycleDemoService implements BeanNameAware, InitializingBean, DisposableBean {
private String beanName;
// 構造函數
public LifecycleDemoService() {
System.out.println("1. 構造函數被調用");
}
// BeanNameAware 接口方法
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("2. Bean 名稱已設置: " + beanName);
}
// @PostConstruct 註解的方法
@PostConstruct
public void init() {
System.out.println("3. @PostConstruct 初始化方法被調用");
}
// InitializingBean 接口方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("4. afterPropertiesSet() 被調用");
}
// 業務方法
public void doSomething() {
System.out.println("5. 執行業務邏輯");
}
// @PreDestroy 註解的方法
@PreDestroy
public void cleanup() {
System.out.println("6. @PreDestroy 清理方法被調用");
}
// DisposableBean 接口方法
@Override
public void destroy() throws Exception {
System.out.println("7. destroy() 被調用");
}
}
這個範例展示了 Bean 生命週期的各個階段。當 Spring 容器啟動時,它會按照特定的順序調用這些方法。理解這個順序對於除錯和優化應用程式非常重要。
3.7 實戰:自定義 Spring Boot 應用程式
現在讓我們通過一個實戰專案來整合我們學到的知識。我們將創建一個可配置的自定義應用程式:
package com.example.myfirstapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@SpringBootApplication
public class MyFirstAppApplication {
public static void main(String[] args) {
SpringApplication.run(MyFirstAppApplication.class, args);
}
}
@Configuration
class CustomConfig {
// 自定義配置 Bean
@Bean
public AppSettings appSettings() {
AppSettings settings = new AppSettings();
settings.setAppName("My First Spring Boot App");
settings.setVersion("1.0.0");
settings.setEnvironment("development");
return settings;
}
}
class AppSettings {
private String appName;
private String version;
private String environment;
// getters and setters
public void printInfo() {
System.out.println("=================================");
System.out.println("應用名稱: " + appName);
System.out.println("版本: " + version);
System.out.println("環境: " + environment);
System.out.println("=================================");
}
// getters and setters
public String getAppName() { return appName; }
public void setAppName(String appName) { this.appName = appName; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public String getEnvironment() { return environment; }
public void setEnvironment(String environment) { this.environment = environment; }
}
現在我們可以在任何地方注入 AppSettings Bean 來使用配置:
package com.example.myfirstapp.service;
import com.example.myfirstapp.AppSettings;
import org.springframework.stereotype.Service;
@Service
public class InfoService {
private final AppSettings appSettings;
// 構造函數注入(推薦方式)
public InfoService(AppSettings appSettings) {
this.appSettings = appSettings;
}
public void displayInfo() {
appSettings.printInfo();
}
}
3.8 常見問題與解答
在這一章中,我們學習了 @SpringBootApplication 的深度知識。以下是一些常見問題的解答:
問題一:為什麼我的 Bean 沒有被注入?這通常是由於元件掃描範圍不正確造成的。確保被標記為 @Component、@Service、@Repository 等註解的類位於主應用程式類的包或其子包中。如果需要掃描其他包,可以使用 @ComponentScan 註解自定義掃描範圍。
問題二:@Configuration 和 @Component 有什麼區別?如前所述,@Configuration 類會被 CGLIB 代理,確保 @Bean 方法之間的依賴關係被正確處理。如果你只需要將類註冊為 Bean,且不需要這種代理行為,可以使用 @Component。
問題三:如何排除特定的自動配置?可以使用 @SpringBootApplication 的 exclude 屬性:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class MyApplication {
// ...
}
Top comments (0)