本章彙整 Spring 開發中最常見的錯誤類型,幫助你快速排查問題。
Bean 定義常見錯誤#
錯誤 1:Bean 掃描不到#
| 症狀 | 啟動時報錯找不到 Bean |
|---|
| 原因 | Bean 不在 @ComponentScan 掃描範圍內 |
| 解決 | 明確指定掃描路徑或調整套件結構 |
// 預設只掃描啟動類所在套件及子套件
@SpringBootApplication
@ComponentScan("com.example") // 明確指定
public class Application { }
錯誤 2:構造器參數找不到對應 Bean#
| 症狀 | Parameter 0 of constructor required a bean that could not be found |
|---|
| 原因 | 顯式定義構造器後,Spring 會嘗試自動裝配參數 |
| 解決 | 確保參數類型有對應的 Bean,或使用 @Autowired(required=false) |
@Service
public class ServiceImpl {
// Spring 會嘗試找一個 String 類型的 Bean 來注入
public ServiceImpl(String serviceName) {
this.serviceName = serviceName;
}
}
// 解決:提供對應的 Bean
@Bean
public String serviceName() {
return "MyServiceName";
}
錯誤 3:存在多個相同類型的 Bean#
| 症狀 | required a single bean, but 2 were found |
|---|
| 原因 | 有多個候選 Bean,Spring 無法決定使用哪個 |
| 解決 | 使用 @Primary、@Qualifier 或變數名匹配 |
// 方案 1:標記 @Primary
@Repository
@Primary
public class OracleDataService implements DataService { }
// 方案 2:使用 @Qualifier
@Autowired
@Qualifier("oracleDataService")
private DataService dataService;
// 方案 3:變數名與 Bean 名一致
@Autowired
private DataService oracleDataService;
依賴注入失敗場景#
錯誤 4:Bean 名稱首字母大小寫問題#
| 症狀 | No qualifying bean of type … available |
|---|
| 原因 | @Qualifier 指定的名稱大小寫錯誤 |
| 規則 | 一般首字母小寫,連續大寫開頭則保持原樣 |
// CassandraDataService -> cassandraDataService
// SQLiteDataService -> SQLiteDataService (連續大寫)
@Autowired
@Qualifier("cassandraDataService") // 注意首字母小寫
private DataService dataService;
錯誤 5:內部類 Bean 引用#
| 症狀 | 找不到內部類 Bean |
|---|
| 原因 | 內部類 Bean 名稱包含外部類名 |
| 格式 | 外部類名.內部類名(外部類首字母小寫) |
public class StudentController {
@Repository
public static class InnerClassDataService implements DataService { }
}
// 引用方式
@Autowired
@Qualifier("studentController.InnerClassDataService")
private DataService innerClassDataService;
錯誤 6:@Value 注入非預期值#
| 症狀 | @Value 注入的值不是組態檔案中的值 |
|---|
| 原因 | 與系統環境變數或系統屬性同名 |
| 解決 | 使用唯一的屬性名稱 |
# application.properties
# 錯誤:username 可能與系統環境變數衝突
username=admin
# 正確:使用有前綴的唯一名稱
app.username=admin
AOP 不生效的原因#
錯誤 7:this 呼叫導致 AOP 失效#
| 症狀 | 切面不執行 |
|---|
| 原因 | this 是原始物件,不是代理物件 |
| 解決 | 自己注入自己或使用 AopContext.currentProxy() |
@Service
public class MyService {
@Autowired
private MyService self; // 注入代理物件
public void methodA() {
self.methodB(); // 通過代理呼叫
}
}
錯誤 8:方法不是 public#
| 症狀 | 切面不執行 |
|---|
| 原因 | Spring AOP 只能代理 public 方法 |
| 解決 | 確保被切面的方法是 public |
錯誤 9:final 類或方法#
| 症狀 | CGLIB 代理失敗 |
|---|
| 原因 | final 類無法被繼承,final 方法無法被覆寫 |
| 解決 | 移除 final 修飾符 |
事務不回滾的情況#
錯誤 10:例外被 catch 吞掉#
| 症狀 | 發生例外但事務沒有回滾 |
|---|
| 原因 | 例外被捕獲後沒有重新拋出 |
| 解決 | 重新拋出例外或手動設定回滾 |
@Transactional
public void createOrder() {
try {
orderDao.insert(order);
} catch (Exception e) {
log.error("失敗", e);
// 方案 1:重新拋出
throw e;
// 方案 2:手動標記回滾
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
錯誤 11:拋出的是 Checked Exception#
| 症狀 | 拋出 Exception 但沒有回滾 |
|---|
| 原因 | 預設只回滾 RuntimeException 和 Error |
| 解決 | 指定回滾的例外類型 |
@Transactional(rollbackFor = Exception.class)
public void createOrder() throws Exception { }
錯誤 12:事務方法被 this 呼叫#
| 症狀 | 事務不生效 |
|---|
| 原因 | 與 AOP 失效原因相同 |
| 解決 | 確保通過代理物件呼叫事務方法 |
Spring Boot 自動組態問題#
錯誤 13:自動組態未生效#
| 症狀 | 預期的自動組態沒有載入 |
|---|
| 可能原因 | 缺少依賴、條件不滿足、被排除 |
| 排查 | 使用 --debug 啟動查看自動組態報告 |
java -jar myapp.jar --debug
# 或在 application.properties 中:
# debug=true
錯誤 14:條件化組態不生效#
| 註解 | 條件 |
|---|
@ConditionalOnClass | 類路徑中存在指定類 |
@ConditionalOnMissingBean | 容器中不存在指定 Bean |
@ConditionalOnProperty | 組態屬性滿足條件 |
@Configuration
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public class FeatureConfig { }
組態屬性綁定問題#
錯誤 15:@ConfigurationProperties 不生效#
| 症狀 | 屬性沒有注入到組態類 |
|---|
| 原因 | 缺少 @EnableConfigurationProperties 或 @ConfigurationPropertiesScan |
| 解決 | 在啟動類上添加相應註解 |
@ConfigurationProperties(prefix = "myapp")
@Data
public class MyAppProperties {
private String name;
}
// 啟用組態屬性
@SpringBootApplication
@EnableConfigurationProperties(MyAppProperties.class)
public class Application { }
錯誤 16:屬性名稱綁定失敗#
| 症狀 | 屬性綁定時報錯或為 null |
|---|
| 原因 | 屬性名稱格式不匹配 |
| 規則 | 支援多種格式:camelCase、kebab-case、snake_case |
# 以下格式都可以綁定到 firstName 屬性
myapp.first-name=John
myapp.firstName=John
myapp.first_name=John
快速排錯指南#
啟動時報錯#
┌─────────────────────────────────────────────────────────────┐
│ Bean 相關啟動錯誤 │
├─────────────────────────────────────────────────────────────┤
│ "No qualifying bean" → 檢查 @ComponentScan、包結構 │
│ "required a single bean, but N were found" → 用 @Qualifier │
│ "Error creating bean" → 檢查依賴、構造器參數 │
│ "BeanCurrentlyInCreationException" → 循環依賴 │
└─────────────────────────────────────────────────────────────┘
執行時問題#
┌─────────────────────────────────────────────────────────────┐
│ 執行時常見問題 │
├─────────────────────────────────────────────────────────────┤
│ AOP 不生效 → 檢查是否 this 呼叫、是否 public 方法 │
│ 事務不回滾 → 檢查例外類型、是否被 catch │
│ 請求 404 → 檢查 URL 映射、掃描範圍 │
│ 請求 400 → 檢查參數類型、格式、驗證 │
│ 請求 500 → 查看例外堆疊 │
└─────────────────────────────────────────────────────────────┘
除錯技巧#
1. 啟用詳細日誌#
# application.yml
logging:
level:
org.springframework: DEBUG
org.springframework.web: DEBUG
org.springframework.transaction: TRACE
2. 檢查 Bean 是否存在#
@Autowired
private ApplicationContext context;
public void checkBeans() {
// 列出所有 Bean
String[] beanNames = context.getBeanDefinitionNames();
// 檢查特定類型的 Bean
Map<String, DataService> beans = context.getBeansOfType(DataService.class);
}
3. 檢查是否為代理物件#
@Autowired
private MyService myService;
public void checkProxy() {
System.out.println("Is proxy: " + AopUtils.isAopProxy(myService));
System.out.println("Is CGLIB proxy: " + AopUtils.isCglibProxy(myService));
System.out.println("Is JDK proxy: " + AopUtils.isJdkDynamicProxy(myService));
}
4. 查看自動組態報告#
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes ...
Negative matches:
-----------------
ActiveMQAutoConfiguration:
- @ConditionalOnClass did not find required class ...
總結:防錯 Checklist#