Spring Bean、IOC/DI 与 Bean 注册机制指南
本文聚焦 Spring 容器中的 Bean、IOC、DI、作用域与注册机制,帮助你建立“对象如何进入容器、如何被注入、为什么能够被自动装配”的基础认知。
第一章 IOC、DI 与三层协作
1.1 三层协作中的依赖关系
为了代码清晰,后端一般会分成三层:
- Controller(控制层):接收请求,响应数据,调用 Service。
- Service(业务逻辑层):处理具体业务。
- Dao / Mapper(数据访问层):直接操作数据库。
在 Spring 项目中,三层协作通常依赖容器来统一管理对象。
1.2 什么是 IOC
- IOC(控制反转):对象的创建和管理权交给 Spring 容器。
- 过去是业务代码自己
new对象,现在是容器负责准备对象,再提供给业务代码使用。
1.3 什么是 DI
- DI(依赖注入):容器自动把目标对象所需的依赖注入进去。
- 常见方式包括字段注入、构造器注入和 Setter 注入。
示例:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
}
@RestController
public class UserController {
@Autowired
private UserService userService;
}
理解这段代码时要抓住两点:
UserServiceImpl与UserController自己不负责依赖对象的创建。- Spring 容器会先管理 Bean,再完成注入关系。
第二章 Bean 的声明与作用域
Bean 是 Spring 容器管理对象的基本单位。理解 Bean 的声明方式、作用域和初始化时机,是理解后续扩展机制的基础。
2.1 Bean 的五种作用域
| 作用域 | 说明 | 快捷注解 |
|---|---|---|
singleton | 默认值,容器内只有一个实例 | @Scope("singleton") |
prototype | 每次获取时创建新的实例 | @Scope("prototype") |
request | 每个 HTTP 请求创建一个实例 | @RequestScope |
session | 每个 HTTP 会话创建一个实例 | @SessionScope |
application | 每个 Web 应用创建一个实例 | @ApplicationScope |
示例:
@Scope("prototype")
@RestController
@RequestMapping("/depts")
public class DeptController {
}
2.2 @RequestScope 的典型用法
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
@Component
@RequestScope
public class UserContext {
private String traceId;
private Long currentUserId;
public void setTraceId(String traceId) {
this.traceId = traceId;
}
public String getTraceId() {
return traceId;
}
public void setCurrentUserId(Long currentUserId) {
this.currentUserId = currentUserId;
}
public Long getCurrentUserId() {
return currentUserId;
}
}
它适合保存一次请求中的上下文信息,例如当前用户、追踪 ID、租户编号等。
2.3 延迟初始化 @Lazy
默认情况下,单例 Bean 会在容器启动时创建。如果你希望第一次真正使用它时再初始化,可以使用 @Lazy。
@Lazy
@RestController
public class DeptController {
public DeptController() {
System.out.println("创建 DeptController 对象...");
}
}
2.4 第三方 Bean 的声明
如果对象来自第三方库,通常不能直接在源码上加 @Component,这时可以通过 @Bean 把它交给容器管理。
@Configuration
public class OSSConfig {
@Bean("customOSS")
public AliyunOSSOperator aliyunOSSOperator(AliyunOSSProperties ossProperties) {
return new AliyunOSSOperator(ossProperties);
}
}
第三章 Bean 注册机制
3.1 为什么第三方 Bean 有时不会生效
有些第三方工具包已经写了 @Component、@Service,但项目引入依赖后仍然注入失败,核心原因通常不是注解失效,而是没有被扫描到。
3.2 扩展组件扫描:@ComponentScan
如果你明确知道目标类所在的包路径,并且对方已经写好了组件注解,可以通过 @ComponentScan 扩展扫描范围。
@SpringBootApplication
@ComponentScan(basePackages = {"com.demo", "com.example.token"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
这种方式的特点是:
- 适合项目内部模块较多、按包组织清晰的场景
- 对包路径有耦合
- 扫描范围越大,启动阶段需要处理的类也越多
3.3 主动导入:@Import
相比扫描,@Import 更像是显式告诉 Spring“请把这些类注册进来”。它适合导入明确的配置类、普通类或扩展类。
导入普通类
@Import(TokenParser.class)
public class AppConfig {
}
导入配置类
@Configuration
public class HeaderConfig {
@Bean
public HeaderParser headerParser() {
return new HeaderParser();
}
@Bean
public HeaderGenerator headerGenerator() {
return new HeaderGenerator();
}
}
@SpringBootApplication
@Import(HeaderConfig.class)
public class Application {
}
导入 ImportSelector
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {
"com.example.token.TokenParser",
"com.example.header.HeaderConfig"
};
}
}
导入 ImportBeanDefinitionRegistrar
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(TokenParser.class);
registry.registerBeanDefinition("tokenParser", beanDefinition);
}
}
3.4 @EnableXxx 的本质
很多注解看起来像是在“开启某项功能”,例如:
@EnableScheduling@EnableAsync@EnableCaching@EnableAspectJAutoProxy
它们的核心思路通常是:通过 @Import 导入一组配置类或扩展类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(HeaderConfig.class)
public @interface EnableHeaderConfig {
}
3.5 @ComponentScan 与 @Import 如何选择
| 方式 | 适合场景 |
|---|---|
@ComponentScan | 项目内部模块较多,希望按包统一扫描 |
@Import | 明确导入少量类、配置类、扩展类,或者封装第三方能力 |
实践中通常可以这样理解:
- 业务代码内部更常用组件扫描
- 第三方能力接入更常用
@Import - 做框架封装、starter 封装时更常用
@Import
本文小结
- IOC 负责把对象创建与管理权交给容器,DI 负责把依赖注入到目标对象中。
- Bean 是容器管理对象的基本单位,需要理解作用域、延迟初始化与第三方 Bean 注册。
@ComponentScan适合按包扫描,@Import适合显式导入。@EnableXxx的底层思想通常仍然建立在@Import之上。