控制器通过构造注入获取服务,本质是Spring IoC容器在启动生命周期中按依赖优先原则完成Bean实例化与装配的结果:先扫描注册控制器BeanDefinition,再确保服务类先行实例化,最后在finishBeanFactoryInitialization阶段注入并初始化,最终使控制器具备处理请求能力。

控制器通过构造注入获取服务,本质是 Spring IoC 容器在启动过程中完成 Bean 实例化与依赖装配的结果。整个过程不是孤立发生的,而是嵌套在 Spring Boot 应用启动的完整生命周期里。关键不在于“写对了 @Autowired”,而在于理解容器何时、按什么顺序、依据什么规则把服务塞进控制器里。
控制器类被扫描并注册为 Bean
Spring Boot 启动时,@ComponentScan 默认扫描启动类所在包及其子包。一旦发现标注了 @RestController(或 @Controller)的类,就会将其解析为一个 BeanDefinition,登记到 BeanFactory 中。此时只是“知道有这个类”,尚未创建实例。
- 类必须在@ComponentScan 范围内,否则不会被发现
- @RestController 是 @Controller + @ResponseBody 的组合,但容器只关心它是否是 Spring 管理的组件
- 若使用 @ComponentScan(basePackages = "...") 显式指定路径,需确保控制器所在包被覆盖
服务类先于控制器完成实例化
构造注入要求依赖对象(即服务)必须在控制器实例化前就绪。Spring 按“依赖优先”原则处理:解析控制器 BeanDefinition 时,发现其构造函数参数类型(如 UserService),就会回溯查找匹配的 UserService 类型 BeanDefinition;若该服务尚未实例化,容器会立即触发它的创建流程——包括实例化、依赖注入、初始化回调等。
- 如果 UserService 本身还依赖 Repository 或 DataSource,这些也会被递归提前加载
- 循环依赖场景下,Spring 会尝试用三级缓存解决,但构造注入无法参与缓存,因此构造注入的循环依赖会直接报错
- 推荐显式声明 @Service 或 @Component,避免仅靠类路径扫描“侥幸命中”
构造函数参数自动匹配并注入
当容器准备实例化控制器时,会检查其构造函数。Spring 会根据参数类型(而非名称)在容器中查找唯一匹配的 Bean。例如:
public UserController(UserService userService) { ... }容器会查找类型为 UserService 的单例 Bean,并将其实例传入构造方法。这一步发生在 finishBeanFactoryInitialization 阶段(refresh() 的第 11 步),属于单例 Bean 的集中初始化环节。
- 若存在多个 UserService 实现,需用 @Primary 或 @Qualifier 明确指定
- 构造注入不可变性高,适合强制依赖;字段注入(@Autowired)则允许为 null,语义不同
- Spring Boot 3.x 默认启用构造注入友好模式,Lombok 的 @RequiredArgsConstructor 可安全配合使用
容器刷新完成,控制器可响应请求
所有单例 Bean(含控制器和服务)实例化并初始化完毕后,refresh() 流程结束,ApplicationContext 进入可用状态。此时内嵌 Tomcat 启动,DispatcherServlet 注册,请求映射(@GetMapping 等)绑定完成。控制器对象已持有所需服务引用,真正具备处理 HTTP 请求的能力。
- 此时访问 /users,DispatcherServlet 找到 UserController 实例,调用其方法,内部直接使用已注入的 userService
- 若服务在初始化阶段抛异常(如数据库连接失败),整个 refresh() 失败,应用启动中止,控制器永远不会进入就绪状态
- 可通过 ApplicationRunner 或 @PostConstruct 验证控制器与服务是否已正确装配

















