Spring Beans 与依赖注入
本教程共 48 篇 · 第 7 篇 · 更新于 2026-08-13 · 约 5 分钟阅读
本节目标:理解 Bean 是什么,学会用注解声明 Bean、用构造器注入组装依赖,掌握作用域与生命周期。
对象谁在管
你写的每个类,都要自己 new 吗?在 Spring 里不用。Spring 容器帮你创建对象、管理对象、销毁对象。被容器管理的对象,叫 Bean。
容器管到什么程度?创建、装配、销毁全包。你只管声明「我要什么」,容器负责「给什么」。这就是控制反转(IoC):创建对象的控制权,从你手里反转给了容器。
依赖注入(DI)是 IoC 的实现手段。对象需要的依赖,容器在创建时主动塞进来,不用你自己找。
声明 Bean:四个「组件」注解
@Component 是通用组件注解。它还有三个语义更明确的兄弟:
| 注解 | 语义 | 典型位置 |
|---|---|---|
@Component | 通用组件 | 工具类、无明确分层 |
@Service | 业务逻辑 | Service 层 |
@Repository | 数据访问 | Dao / Repository 层 |
@Controller | 接收请求 | Web 层 |
它们功能上等价,都能让类变成 Bean。区别在语义和附加行为。@Repository 会把持久层异常翻译成 Spring 的数据访问异常;@Controller 会被 Spring MVC 识别为控制器。分层写清楚,代码可读性高,AOP 也好定位。
一个简单的 Service Bean:
package com.example.demo;
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
public String greet(String name) {
return "你好," + name + "!";
}
}
找到 Bean:@ComponentScan
注解标了,容器怎么知道?靠组件扫描。@SpringBootApplication 自带 @ComponentScan,默认扫描主类所在包及子包。所以第 3 章强调主类要放顶层包——包放错了,Bean 扫不到,启动报「No qualifying bean」错误。
WarningBean 的类必须放在主类所在包的子包里。初学者最常见的错误:主类放在
com.example,业务类放在com.other,结果一个都扫不到。
Bean 默认名字是类名首字母小写:GreetingService 的 Bean 名是 greetingService。@Autowired 按类型找 Bean,一般用不上名字;同名冲突时,容器启动会直接报错。
注入依赖:构造器注入是主流
Bean 之间要协作。GreetingController 要用 GreetingService,怎么拿?Spring 官方推荐构造器注入:把依赖写进构造器参数。
package com.example.demo;
import org.springframework.stereotype.Controller;
@Controller
public class GreetingController {
private final GreetingService greetingService;
public GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
}
容器创建 GreetingController 时,自动找 GreetingService 的 Bean 传进来。只有一个构造器时,不用加任何注解,Spring 直接选它。
构造器注入好在哪?依赖字段能标 final,对象一旦创建,依赖不可变。测试也好写——new GreetingController(mockService) 直接传假对象,不用起容器。
构造器多于一个,必须用 @Autowired 指明用哪个:
package com.example.demo;
import java.io.PrintStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MyAccountService {
private final GreetingService greetingService;
private final PrintStream out;
@Autowired
public MyAccountService(GreetingService greetingService) {
this.greetingService = greetingService;
this.out = System.out;
}
public MyAccountService(GreetingService greetingService, PrintStream out) {
this.greetingService = greetingService;
this.out = out;
}
}
字段注入长这样,很多旧教程这么写:
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class GreetingController {
@Autowired
private GreetingService greetingService;
}
代码最短,但缺点明显:字段不能 final,测试必须靠容器,依赖关系藏在字段里。对照可以,主线别用。
Setter 注入适合「可选依赖」,用得也不多。记住一句话:默认构造器注入,可选依赖用 setter,字段注入避开。
Note版本差异:老教程里注入的是
javax.annotation、javax.inject这些包。Spring Boot 4 基于 Jakarta EE 11,主线代码统一jakarta.*。看到javax.*的代码,别直接抄进新项目。
手动声明 Bean:@Bean
第三方类没法加 @Component 注解,就用 @Bean 手动声明。放在 @Configuration 类里:
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public GreetingService greetingService() {
return new GreetingService();
}
}
方法名是 Bean 名,返回值是 Bean 本身。凡是 new 出来的对象想交给容器管,都走这条路。RestClient 这类外部客户端,常用 @Bean 声明后再注入。
作用域:Bean 活多久
Bean 默认是单例(singleton):整个容器里只有一个实例,大家都用它。适合无状态的服务类,性能最好。
需要每次拿新实例,用 prototype:
package com.example.demo;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class Counter {
private int count;
public void increment() {
count++;
}
}
Web 应用还有三个作用域:request(每次请求一个实例)、session(每个会话一个)、application(ServletContext 级别一个),只在 Web 环境可用。
Tip90% 的场景用默认单例就够。
prototype用在「有状态、要隔离」的对象上。初学者先记住这两个,Web 的三个用到再查。
单例 Bean 是共享的,天然要小心多线程。Bean 里别放「会变的成员变量」——多个请求同时改同一个字段,数据就乱了。无状态(不存可变状态)是单例 Bean 的基本修养。
生命周期:出生与善后
Bean 创建后、销毁前,可以挂回调。@PostConstruct 在依赖注入完成后执行,常用来做初始化;@PreDestroy 在容器关闭前执行,常用来释放资源。注意包名是 jakarta.annotation:
package com.example.demo;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class CacheWarmer {
@PostConstruct
public void init() {
System.out.println("启动完成,预热缓存");
}
@PreDestroy
public void cleanup() {
System.out.println("关闭前,释放资源");
}
}
Spring 容器关闭时,会按依赖关系逆序销毁 Bean,依次调用 @PreDestroy。日志里看到「Closing org.springframework.context…」时,就是容器在收尾。
@Bean 声明时也能指定回调方法:
@Bean(initMethod = "init", destroyMethod = "close")
public SomeClient someClient() {
return new SomeClient();
}
destroyMethod 默认值很聪明:容器会尝试调用 Bean 的 close() 或 shutdown() 方法。有这类方法的外部资源类,销毁不用你操心。
Tip初始化逻辑优先放
@PostConstruct,别在构造器里做重活。构造器执行时依赖还没注入完,而且构造器里抛异常,排查起来更费劲。
小结
- Bean 是容器管理的对象,注解声明 + 组件扫描注册。
- 构造器注入是官方推荐,字段注入仅作对照。
- 单例是默认作用域,
prototype每次新建。 @PostConstruct/@PreDestroy管出生与善后。