Spring依赖注入与自动装配解析
写在前面
如果你想深入学习SpringBoot的自动配置原理,那么本文是你必须掌握的基础知识。很多人在使用SpringBoot时,只知道用@Autowired注入依赖,却不知道背后的原理。
本文将从最基础的概念讲起,带你彻底理解Spring的依赖注入机制。无论你是刚接触Spring的新手,还是想复习巩固的老手,都能从本文获益。
适合人群: Spring初学者、准备深入学习SpringBoot的开发者、需要复习基础的工程师
一、核心概念:什么是Bean、依赖和注入
在学习依赖注入之前,我们必须先理解三个基本概念。
1.1 什么是Bean?
Bean就是由Spring容器管理的对象。
在传统Java开发中,我们这样创建对象:
UserService userService = new UserService();
而在Spring中,我们不需要自己new对象,而是让Spring容器帮我们创建和管理:
@Service // 告诉Spring:这是一个Bean,请帮我管理。@Service是@Component的衍生注解
public class UserService {
// …
}
形象的比喻:
- 传统方式:你需要什么对象,自己new一个(自己做饭)
- Spring方式:你告诉Spring需要什么,Spring给你创建好(外卖送餐)
Bean的特点:
- 由Spring容器创建
- 由Spring容器管理生命周期
- 默认是单例的(整个应用只有一个实例)
- 可以被其他Bean引用
1.2 什么是依赖?
依赖就是一个对象需要使用另一个对象来完成工作。
举个例子:
public class UserService {
private UserDao userDao; // UserService依赖UserDao
public User getUser(Long id) {
return userDao.findById(id); // 需要用到UserDao
}
}
在这个例子中:
- UserService 需要 UserDao 才能完成查询用户的功能
- 我们说:UserService 依赖 UserDao
生活中的例子:
- 你要开车上班,你依赖汽车
- 你要做饭,你依赖厨具
- 你要写代码,你依赖电脑
1.3 什么是注入依赖?
注入依赖就是把一个对象需要的其他对象"送"给它。
传统方式(自己创建依赖):
public class UserService {
private UserDao userDao;
public UserService() {
this.userDao = new UserDaoImpl(); // 自己创建依赖
}
}
依赖注入方式(Spring帮你注入):
@Service
public class UserService {
@Autowired // 告诉Spring:请把UserDao注入给我
private UserDao userDao;
}
形象的比喻:
- 传统方式:你要喝咖啡,自己买豆子、磨豆、煮咖啡(自己创建依赖)
- 依赖注入:你要喝咖啡,告诉咖啡店,咖啡店做好送给你(Spring注入依赖)
依赖注入的好处:
二、控制反转(IoC):理解Spring的核心思想
2.1 什么是控制反转?
控制反转(Inversion of Control,IoC)是一种设计思想:把对象创建和管理的控制权交给框架。
简单来说:就相当于框架帮你创建对象,只创建一次,那个类调用就把对象注入给谁
好处:
- 单例复用:框架创建的 Bean 默认是单例(只创建 1 次),所有需要这个对象的类,注入的都是同一个实例,避免重复创建对象浪费内存(比如一个UserDao对象,所有Service都用同一个,不用每次都new);
- 生命周期托管:对象的 “创建→初始化→使用→销毁” 全由框架控制,你可以通过注解(如@PostConstruct初始化、@PreDestroy销毁)自定义流程,不用手动管理对象的生灭。
传统方式: 你控制一切
public class UserService {
private UserDao userDao;
public UserService() {
// 你决定创建什么对象
this.userDao = new UserDaoImpl();
}
}
IoC方式: Spring控制一切
@Service
public class UserService {
@Autowired
private UserDao userDao; // Spring决定注入什么对象
}
"反转"体现在哪里?
| 谁创建对象 | 你自己new | Spring容器创建 |
| 谁管理对象 | 你自己管理 | Spring容器管理 |
| 谁决定依赖 | 你在代码中写死 | Spring根据配置决定 |
核心思想: 不要打电话给我们,我们会打电话给你(Don’t call us, we’ll call you)
2.2 IoC容器的作用
Spring的IoC容器(ApplicationContext)就像一个对象工厂,负责:
// Spring容器启动时
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 从容器获取Bean
UserService userService = context.getBean(UserService.class);
// 这个userService已经注入好了所有依赖,可以直接使用
User user = userService.getUser(1L);
三、依赖注入的三种方式
Spring支持三种依赖注入方式,每种都有各自的适用场景。
方式1:字段注入(最常见但不推荐)
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private EmailService emailService;
}
优点: 代码简洁,写起来方便
缺点:
- 无法注入final字段(不能保证不可变性)
- 不利于单元测试(必须启动Spring容器或使用反射)
- 违反了面向对象的封装原则
- 依赖关系不明确
方式2:构造器注入(强烈推荐)
@Service
//@RequiredArgsConstructor
//Lombok注解,写了这个就不用写构造器了,但是要加入Lombok依赖。(依赖在博主之前的关于Lombok的文章中有提到过)
public class UserService {
private final UserDao userDao;
private final EmailService emailService;
// Spring 4.3+ 单构造器可省略@Autowired
public UserService(UserDao userDao, EmailService emailService) {
this.userDao = userDao;
this.emailService = emailService;
}
}
优点:
- 可以使用final,保证不可变性和线程安全
- 便于单元测试(直接new对象传参,无需Spring容器)
- 依赖关系一目了然
- 如果依赖过多,构造器参数会很长,提醒你类职责可能过重
这是Spring官方推荐的方式!
方式3:Setter方法注入
@Service
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
优点: 可以在对象创建后重新注入依赖(适合可选依赖)
缺点: 依赖可能为null,不够安全
三种方式对比:
| 代码简洁度 | 最简洁 | 中等 | 较繁琐 |
| 是否支持final | 否 | 是 | 否 |
| 测试友好度 | 差 | 好 | 中等 |
| 依赖明确度 | 不明确 | 非常明确 | 较明确 |
| 官方推荐度 | 不推荐 | 强烈推荐 | 适合可选依赖 |
四、@Autowired与自动装配详解
4.1 什么是自动装配?
自动装配(Auto-Wiring)是Spring自动为Bean注入依赖的机制。
简单来说:你只需要告诉Spring"我需要这个依赖"(通过@Autowired注解),Spring会自动帮你找到合适的Bean并注入进来。
传统方式(手动装配):
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<bean id="userService" class="com.example.service.UserService">
<property name="userDao" ref="userDao"/> <!– 手动指定依赖 –>
</bean>
自动装配方式:
@Service
public class UserService {
@Autowired // 自动装配,Spring自动找到UserDao并注入
private UserDao userDao;
}
4.2 自动装配的演进历史
阶段1:XML时代(Spring 1.x – 2.x)
<!– 按类型自动装配 –>
<bean id="userService" class="com.example.service.UserService"
autowire="byType"/>
<!– 按名称自动装配 –>
<bean id="userService" class="com.example.service.UserService"
autowire="byName"/>
阶段2:注解时代(Spring 2.5+)
@Service
public class UserService {
@Autowired // 注解方式,更简洁
private UserDao userDao;
}
阶段3:纯Java配置时代(Spring 3.0+)
Spring 3.0 引入了 @Configuration 和 @Bean:
@Configuration
public class AppConfig {
@Bean
public UserDao userDao() {
return new UserDaoImpl();
}
@Bean
public UserService userService() {
return new UserService(); // 自动装配
}
}
阶段4:SpringBoot时代(Spring 4.0+)
@SpringBootApplication // 自动扫描,自动装配
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.3 自动装配的工作原理
核心处理器: AutowiredAnnotationBeanPostProcessor
完整流程:
1. Spring容器启动,扫描所有Bean定义
↓
2. 创建Bean实例(调用构造函数)
↓
3. AutowiredAnnotationBeanPostProcessor介入
↓
4. 扫描Bean中的@Autowired注解
↓
5. 解析依赖类型
↓
6. 在容器中查找匹配的Bean
├─ 按类型查找(byType)- 默认策略
├─ 如果有多个,按@Primary筛选
├─ 如果还有多个,按@Qualifier筛选
└─ 如果还有多个,按字段名称匹配
↓
7. 通过反射注入依赖
↓
8. Bean初始化完成
4.4 自动装配的匹配策略
策略1:按类型匹配(byType)- 默认
@Service
public class UserService {
@Autowired
private UserDao userDao; // 查找UserDao类型的Bean
}
- 找到1个 → 直接注入
- 找到0个 → 抛出异常(除非required=false)
- 找到多个 → 继续下一步匹配
策略2:按@Primary匹配 – 优先级
@Repository
@Primary // 标记为首选
public class UserDaoMysqlImpl implements UserDao { }
@Repository
public class UserDaoMongoImpl implements UserDao { }
@Service
public class UserService {
@Autowired
private UserDao userDao; // 自动选择@Primary标记的Bean
}
策略3:按@Qualifier匹配 – 明确指定
@Service
public class UserService {
@Autowired
@Qualifier("userDaoMongoImpl") // 明确指定使用哪个Bean
private UserDao userDao;
}
策略4:按字段名称匹配 – 辅助
@Service
public class UserService {
@Autowired
private UserDao userDaoMysqlImpl; // 字段名与Bean名称一致
}
策略5:注入集合 – 获取所有实现
@Service
public class UserService {
// 注入所有UserDao类型的Bean
@Autowired
private List<UserDao> userDaoList;
// 注入所有UserDao类型的Bean到Map,key为Bean名称
@Autowired
private Map<String, UserDao> userDaoMap;
}
匹配优先级:
@Qualifier(最高优先级)
↓
@Primary
↓
字段名称匹配
↓
类型匹配(默认)
4.5 处理多个候选Bean
当容器中有多个相同类型的Bean时:
// 有两个UserDao的实现
@Repository
public class UserDaoMysqlImpl implements UserDao { }
@Repository
public class UserDaoMongoImpl implements UserDao { }
// 注入时会报错
@Service
public class UserService {
@Autowired
private UserDao userDao; // 错误:找到2个候选Bean
}
解决方案总结:
| @Qualifier | @Qualifier("userDaoMysqlImpl") | 明确知道要用哪个Bean |
| @Primary | 在Bean上加@Primary | 有一个默认首选实现 |
| 字段名匹配 | private UserDao userDaoMysqlImpl | 简单场景 |
| 注入List/Map | List<UserDao> userDaoList | 需要使用所有实现 |
4.6 可选依赖的处理
方式1:使用required=false
@Service
public class UserService {
@Autowired(required = false) // 找不到Bean不报错,注入null
private CacheService cacheService;
public User getUser(Long id) {
if (cacheService != null) {
User cached = cacheService.get(id);
if (cached != null) return cached;
}
return userDao.findById(id);
}
}
方式2:使用Optional(更优雅)
@Service
public class UserService {
@Autowired
private Optional<CacheService> cacheService;
public User getUser(Long id) {
return cacheService
.map(cache -> cache.get(id))
.orElseGet(() -> userDao.findById(id));
}
}
4.7 自动装配的注意事项
注意1:理解装配时机
自动装配发生在Bean的属性注入阶段,在构造函数之后:
@Service
public class UserService {
@Autowired
private UserDao userDao;
public UserService() {
// 错误:此时userDao还未注入,为null
// userDao.findAll();
}
@PostConstruct
public void init() {
// 正确:此时userDao已经注入
userDao.findAll();
}
}
注意2:避免循环依赖
// 不推荐:循环依赖
@Service
public class UserService {
@Autowired
private OrderService orderService;
}
@Service
public class OrderService {
@Autowired
private UserService userService;
}
// 推荐:重新设计,单向依赖
@Service
public class OrderService {
@Autowired
private UserService userService; // 单向依赖
}
4.8 自动装配与SpringBoot自动配置的区别
很多人容易混淆这两个概念:
自动装配(Auto-Wiring):
- Spring的核心功能
- 负责注入依赖
- 通过@Autowired实现
- Bean已存在,只是注入
自动配置(Auto-Configuration):
- SpringBoot的特性
- 负责创建Bean
- 通过@EnableAutoConfiguration实现
- 根据条件自动创建Bean
关系:
SpringBoot自动配置 → 创建Bean
↓
Spring自动装配 → 注入Bean
举例:
// SpringBoot自动配置:自动创建DataSource Bean
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
// 自动装配:自动注入DataSource依赖
@Service
public class UserService {
@Autowired // 自动装配
private DataSource dataSource; // 这个Bean是自动配置创建的
}
五、其他注入注解对比
5.1 @Resource(JSR-250规范)
@Service
public class UserService {
@Resource // 默认按名称装配
private UserDao userDao;
@Resource(name = "userDaoMysqlImpl") // 指定Bean名称
private UserDao userDao2;
}
@Autowired vs @Resource:
| 来源 | Spring框架 | Java EE规范 |
| 默认装配方式 | 按类型(byType) | 按名称(byName) |
| 指定Bean | 配合@Qualifier | 使用name属性 |
| 支持required | 支持 | 不支持 |
| 支持构造器注入 | 支持 | 不支持 |
建议: 优先使用@Autowired,功能更强大,是Spring原生注解。
5.2 @Inject(JSR-330)
来自Java依赖注入规范:
@Service
public class UserService {
@Inject // 功能类似@Autowired
private UserDao userDao;
}
需要额外引入依赖:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
@Inject vs @Autowired:
| 来源 | Spring | JSR-330 |
| 是否支持required | 支持 | 不支持 |
| 配合注解 | @Qualifier | @Named |
建议: 除非需要跨框架兼容,否则使用 @Autowired 即可。
5.3 @Value(注入配置值)
用于注入配置文件中的值,这是学习SpringBoot配置的基础:
@Service
public class UserService {
@Value("${app.name}") // 从配置文件读取
private String appName;
@Value("${app.max-users:100}") // 指定默认值100
private int maxUsers;
@Value("#{systemProperties['user.home']}") // SpEL表达式
private String userHome;
}
配置文件(application.properties):
app.name=我的应用
app.max-users=500
六、常见问题与解决方案
问题1:循环依赖
什么是循环依赖?
两个或多个Bean相互依赖,形成闭环:
@Service
public class UserService {
@Autowired
private OrderService orderService; // UserService依赖OrderService
}
@Service
public class OrderService {
@Autowired
private UserService userService; // OrderService依赖UserService
}
Spring如何解决?
Spring通过三级缓存机制解决单例Bean的字段注入循环依赖:
无法解决的情况:构造器循环依赖
@Service
public class UserService {
private final OrderService orderService;
public UserService(OrderService orderService) {
this.orderService = orderService;
}
}
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService; // 报错!无法解决
}
}
解决方法:
@Service
public class UserService {
private final OrderService orderService;
public UserService(@Lazy OrderService orderService) {
this.orderService = orderService; // 注入代理对象,延迟初始化
}
}
问题2:注入的Bean为null
常见原因:
原因1:类没有被Spring管理
// 错误:忘记加@Service注解
public class UserService {
@Autowired
private UserDao userDao; // 注入失败,为null
}
// 正确:添加@Service注解
@Service
public class UserService {
@Autowired
private UserDao userDao; // 正常注入
}
原因2:包扫描路径不对
@SpringBootApplication
@ComponentScan("com.example.service") // 只扫描service包
public class Application {
// UserDao在com.example.dao包下,扫描不到
}
// 正确:扫描父包
@SpringBootApplication
@ComponentScan("com.example") // 扫描整个com.example包
public class Application { }
原因3:直接new对象,不从Spring容器获取
// 错误:直接new的对象,Spring无法注入依赖
UserService service = new UserService();
service.getUser(1L); // userDao为null,报NPE
// 正确:从Spring容器获取
@RestController
public class UserController {
@Autowired
private UserService userService; // 从容器获取,依赖已注入
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id); // 正常使用
}
}
原因4:在静态字段上使用@Autowired
// 错误:无法注入静态字段
@Service
public class UserService {
@Autowired
private static UserDao userDao; // 无法注入
}
// 正确:使用非静态字段
@Service
public class UserService {
@Autowired
private UserDao userDao; // 正常注入
}
问题3:注入时机问题
错误示例:在构造函数中使用依赖
@Service
public class UserService {
@Autowired
private UserDao userDao;
private List<User> cache;
// 错误:构造函数执行时,userDao还未注入
public UserService() {
this.cache = userDao.findAll(); // NPE!
}
}
正确做法:使用@PostConstruct
@Service
public class UserService {
@Autowired
private UserDao userDao;
private List<User> cache;
@PostConstruct // 在依赖注入完成后执行
public void init() {
this.cache = userDao.findAll(); // 正确
}
}
Bean的生命周期:
1. 实例化(执行构造函数)
↓
2. 依赖注入(@Autowired生效)
↓
3. 初始化前(@PostConstruct执行)
↓
4. 初始化(InitializingBean.afterPropertiesSet())
↓
5. Bean可用
↓
6. 销毁前(@PreDestroy执行)
↓
7. 销毁(DisposableBean.destroy())
七、最佳实践
1. 优先使用构造器注入
// 推荐
@Service
public class UserService {
private final UserDao userDao; // final保证线程安全
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
//或者可以这样写,一个意思
@Service
@@RequiredArgsConstructor
public class UserService {
private final UserDao userDao;
}
2. 使用final保证不可变性
@Service
public class UserService {
private final UserDao userDao; // final保证线程安全
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
3. 多个相同类型Bean时明确指定
@Service
public class UserService {
private final UserDao userDao;
public UserService(@Qualifier("userDaoMysqlImpl") UserDao userDao) {
this.userDao = userDao;
}
}
4. 可选依赖使用Optional
@Service
public class UserService {
@Autowired
private Optional<CacheService> cacheService;
}
5. 避免循环依赖
重新设计类的职责,消除循环依赖。如果实在无法避免,使用@Lazy。
八、面试高频问题
问题1:@Autowired和@Resource的区别?
答案:
| 来源 | Spring框架 | Java EE规范(JSR-250) |
| 装配方式 | 默认byType,可配合@Qualifier按名称 | 默认byName,可通过name属性指定 |
| 作用范围 | 字段、构造器、方法、参数 | 字段、方法 |
| required属性 | 支持 | 不支持 |
| 推荐使用 | Spring项目推荐 | 需要跨框架兼容时使用 |
问题2:什么是循环依赖?Spring如何解决?
答案:
循环依赖是指两个或多个Bean相互依赖,形成闭环。
Spring通过三级缓存解决单例Bean的循环依赖:
注意: 构造器循环依赖无法解决,需要使用@Lazy或重新设计。
问题3:为什么推荐使用构造器注入?
答案:
问题4:@Autowired的装配流程是什么?
答案:
- 先按类型查找
- 如果有多个,按@Primary筛选
- 如果还有多个,按@Qualifier筛选
- 如果还有多个,按字段名称匹配
问题5:如何解决多个相同类型Bean的注入问题?
答案:
问题6:依赖注入和工厂模式有什么区别?
答案:
工厂模式:
- 对象自己通过工厂获取依赖
- 对象知道工厂的存在
- 耦合度较高
依赖注入:
- 容器主动将依赖注入到对象
- 对象不知道容器的存在
- 完全解耦
依赖注入是控制反转的一种实现方式,比工厂模式更彻底地解耦。
九、与SpringBoot的关系
理解了Spring的依赖注入,你就能更好地理解SpringBoot的自动配置原理。
SpringBoot如何简化依赖注入?
@SpringBootApplication // 包含@ComponentScan,自动扫描当前包及子包
//目标类标注 @Component 或其衍生注解,且类处于启动类所在包/子包下,SpringBoot 就会自动扫描并将其注册为 Bean 实例。
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
SpringBoot会自动配置常用的Bean(如DataSource、JdbcTemplate等),你只需注入使用:
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate; // SpringBoot自动配置,直接注入
}
引入starter依赖,相关Bean自动配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
@Service
public class UserService {
@Autowired
private EntityManager entityManager; // 自动配置,直接注入
}
本文是学习SpringBoot的基础:
- SpringBoot的自动配置本质上就是自动创建Bean并注入依赖
- 理解了依赖注入,才能理解@ConditionalOnBean等条件注解
- 理解了依赖注入,才能理解Starter的工作原理
下一步学习: 阅读《SpringBoot自动配置原理深度解析》,理解SpringBoot如何自动创建和配置Bean。
十、推荐资源
官方文档
- Spring Framework Reference:https://docs.spring.io/spring-framework/docs/current/reference/html/
- Spring IoC Container:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans
源码阅读
- Spring Framework:https://github.com/spring-projects/spring-framework
- 重点类:
- AutowiredAnnotationBeanPostProcessor
- DefaultListableBeanFactory
- AbstractAutowireCapableBeanFactory
十一、总结
核心要点:
学习路线:
Spring依赖注入(本文)
↓
SpringBoot自动配置原理
↓
自定义Starter开发
↓
Spring AOP原理
↓
Spring事务管理
给读者的建议:
如果这篇文章对你有帮助,请点赞、收藏、关注!有问题欢迎在评论区讨论。
作者:[识君啊]
不要做API的搬运工,要做原理的探索者!
网硕互联帮助中心



评论前必须登录!
注册