Spring:容器与依赖注入¶
Spring IoC 容器负责创建、配置和管理对象。交给容器管理的对象称为 Bean;Bean 之间的依赖可以通过构造器、工厂方法参数等方式提供。
本例使用 JDK 21、Maven、Spring Framework 6.2.12,通过 Java 配置创建一个最小容器,不涉及 Web 服务。
核心概念¶
| 名称 | 作用 |
|---|---|
ApplicationContext |
访问容器中的对象及相关应用能力 |
@Configuration |
声明提供 Bean 定义的配置类 |
@Bean |
将方法创建的对象注册为 Bean |
@ComponentScan |
扫描指定包,注册带组件注解的类 |
| 依赖注入 | 由容器为对象提供它依赖的其他对象 |
Bean 的默认作用域是 singleton,表示同一个容器中相应 Bean 定义通常对应一个共享实例;这不意味着这个类在整个 JVM 中只能有一个对象。
| 作用域 | 行为 |
|---|---|
singleton |
容器内共享一个实例,默认值 |
prototype |
每次获取时创建新的实例 |
request、session 等 |
Web 应用中与请求或会话生命周期绑定 |
组件扫描¶
除了在配置类中逐个声明 @Bean,还可以让容器扫描带组件注解的类:
@Component
public class StudyService {
private final Formatter formatter;
public StudyService(Formatter formatter) {
this.formatter = formatter;
}
}
配合 @ComponentScan("learning") 或 @SpringBootApplication 等入口,容器会自动注册组件并注入其构造器依赖。@Service、@Repository 等是 @Component 的语义化特化,便于按职责区分。
生命周期¶
容器创建 Bean 并完成注入后,会调用标注 @PostConstruct 的方法;关闭上下文时,会调用标注 @PreDestroy 的方法:
初始化失败会阻止 Bean 投入使用。销毁回调依赖上下文正常关闭,进程被强制终止时不会执行。
创建项目¶
将以下内容保存为 pom.xml。版本固定用于复现本例:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>learning</groupId>
<artifactId>spring-example</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
</plugin>
</plugins>
</build>
</project>
示例:让容器组装学习服务¶
Main.java
package learning;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class Main {
public interface Formatter {
String format(String topic);
}
public static class StudyService {
private final Formatter formatter;
public StudyService(Formatter formatter) {
this.formatter = formatter;
}
public String start(String topic) {
return formatter.format(topic);
}
}
@Configuration
public static class Config {
@Bean
public Formatter formatter() {
return topic -> "Ready: " + topic;
}
@Bean
public StudyService studyService(Formatter formatter) {
return new StudyService(formatter);
}
}
public static void main(String[] args) {
try (var context = new AnnotationConfigApplicationContext(Config.class)) {
StudyService service = context.getBean(StudyService.class);
System.out.println(service.start("Java"));
}
}
}
在项目目录执行,首次运行需要下载 Maven 依赖:
输出:
容器先读取配置类,注册两个 Bean,再为 studyService 方法的 Formatter 参数提供相应对象。业务服务通过构造器接收依赖,自己不需要查找容器。
常见配置问题¶
| 现象 | 可以检查 |
|---|---|
| 找不到 Bean | 配置是否被加载,Bean 是否确实注册 |
| 同一类型存在多个候选对象 | 使用名称、@Qualifier 或 @Primary 明确选择 |
| 注解未产生预期行为 | 是否由容器创建对象,相关处理器是否启用 |
| 容器关闭后资源仍未释放 | Bean 的销毁方法和资源所有权是否清晰 |
注意事项¶
- 普通
new StudyService(...)创建的对象不会自动获得所有容器扩展行为。 - 默认共享 Bean 可能被多个线程调用,需要控制可变状态。
- Spring Framework 与 Spring Boot 的配置入口不同,本例使用前者的基础容器。
- 关闭应用上下文,使容器有机会执行已注册的销毁回调。
练习¶
增加第二种 Formatter 实现,观察仅按类型注入时的歧义,再使用 @Qualifier 明确选择。保留服务类,让变化集中在配置中。