報
NoSuchBeanDefinitionException: No qualifying bean of type ... available十有八九不是程式碼寫錯,是 Spring 壓根沒掃到這個類。Spring Boot 預設只掃描主類所在包和它的子包,Bean 定義在別的包、或少了@Service這類註解,容器裡就沒有它。
Issue & Env(案發現場與環境)
- 業務場景:分模塊或多包工程裡,
UserService定義在一個包,UserController在另一個包,注入時啟動失敗;或 Bean 類漏寫@Service/@Component。 - 環境:Spring Boot 2.x / 3.x 通用。預設元件掃描行為由
@SpringBootApplication裡的@ComponentScan決定。
StackTrace(堆疊日誌)
典型的注入失敗堆疊,UnsatisfiedDependencyException 包著 NoSuchBeanDefinitionException:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userController' defined in file [/Users/dev/demo/target/classes/com/example/demo/UserController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.service.UserService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:911)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:788)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1388)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1341)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:612)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:962)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:930)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:596)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:405)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:305)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1307)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1296)
at com.example.demo.Application.main(Application.java:12)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.service.UserService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:2126)
at org.springframework.beans.factory.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:2093)
at org.springframework.beans.factory.DefaultListableBeanFactory.doResolveBeanForType(DefaultListableBeanFactory.java:1747)
at org.springframework.beans.factory.DefaultListableBeanFactory.doResolveMultipleBeans(DefaultListableBeanFactory.java:1383)
at org.springframework.beans.factory.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1313)
at org.springframework.beans.factory.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1575)
at org.springframework.beans.factory.DefaultListableBeanFactory.doResolveMultipleBeans(DefaultListableBeanFactory.java:1351)
at org.springframework.beans.factory.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1313)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AutowireCapableBeanFactory.java:738)
... 19 more
Reproduce(復現程式碼)
主類放在 com.example.demo,Service 卻放在平級之外的 com.example.service:
// com/example/demo/Application.java
package com.example.demo;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// com/example/service/UserService.java —— 在 com.example.demo 之外
package com.example.service;
@Service
public class UserService {
public void hello() {}
}
// com/example/demo/UserController.java
package com.example.demo;
@RestController
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
}
啟動即拋上面那段 NoSuchBeanDefinitionException。UserService 有 @Service 註解,但它在預設掃描路徑之外,容器沒把它登記為 Bean。
Root Cause(根因分析)
@SpringBootApplication 是一個組合註解,其中包含 @ComponentScan。@ComponentScan 沒有顯式指定 basePackages 時,掃描起點是聲明這個註解的類所在的包——也就是主類 Application 所在的 com.example.demo,以及它的所有子包。
com.example.service 和 com.example.demo 是平級兄弟包,既不是子包也不包含主類包,自然不在掃描範圍內。於是 UserService 有註解也不會被 ClassPathScanningCandidateComponentProvider 找到,容器裡沒有「類型為 UserService 的 Bean」,注入時報 NoSuchBeanDefinitionException。
另一個高頻變體是註解漏寫:類沒有 @Service / @Component / @Repository,即使在同一包內也不會被掃描登記。
還有一個更容易翻車的寫法——把 @ComponentScan 的範圍寫錯。spring-boot issue #16063 裡,有人在主類上寫 @ComponentScan("*.*"),掃描範圍直接擴到整個依賴 jar,連 spring-boot-autoconfigure 裡的自動設定類都被掃進去,報的是 BeanDefinitionStoreException。根子是同一個:@ComponentScan 的邊界沒搞清。
The Fix(解決方案)
首選:把 Bean 挪到主類包下面
最省事也最乾淨,讓目錄跟隨主類包:
com.example.demo
├── Application.java
├── controller
│ └── UserController.java
└── service
└── UserService.java
service 變成 com.example.demo 的子包,預設掃描能覆蓋,無需任何額外設定。
跨包場景:顯式聲明掃描範圍
Bean 在 Maven/Gradle 多模塊或第三方包裡,就主動擴大掃描範圍:
@SpringBootApplication(scanBasePackages = {
"com.example.demo",
"com.example.service"
})
public class Application {}
或單獨標註:
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.demo", "com.example.service"})
public class Application {}
註解漏寫:補上 stereotype
確認類裡有對應註解:
@Service
public class UserService { ... }
Mapper 接口這類非 @Component 的 Bean,配合 @MapperScan("com.example.demo.mapper") 顯式指定掃描包,別指望預設元件掃描。
