深入拆解Spring思想:DI(依赖注入)
- 在简单了解IoC与DI中我们已经了解了DI的基本操作,接下来我们来详解DI。(IoC详解请看这里)
- 我们已经知道DI是“你给我,我不用自己创建”的原则。现在我们来看看Spring是如何实现“给”这个动作的,也就是依赖注入的几种方式。
Spring主要提供了三种注入方式,都是通过 @Autowired 注解配合完成的:
- 属性注入 (Field Injection):直接在字段上使用
@Autowired。 - 构造方法注入 (Constructor Injection):在类的构造方法上使用
@Autowired。 - Setter 注入 (Setter Injection):在Setter方法上使用
@Autowired。
三种注入方式
属性注入
方式:直接在需要注入的属性(字段)上加上 @Autowired。
代码示例:
首先,我们有一个 UserService Bean:
@Service // 告诉Spring:我是UserService,请你管理我
public class UserService {public void sayHi() {System.out.println("Hi, UserService");}
}
然后,在 UserController 中注入 UserService:
@Controller
public class UserController {@Autowired // 告诉Spring:我需要一个UserService,请你直接注入到这个属性里private UserService userService;public void sayHi() {System.out.println("Hi, UserController...");userService.sayHi(); // 使用注入的UserService}
}
获取并使用:
@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);UserController userController = context.getBean(UserController.class);userController.sayHi();}
}
运行结果:
Hi, UserController...
Hi, UserService
解释:Spring成功地将 UserService 实例注入到了 UserController 的 userService 属性中。
注意:如果你尝试在没有Spring容器的情况下直接调用 UserController 的 sayHi() 方法,userService 会是 null,导致空指针异常(NPE),因为Spring没有机会注入它。
构造方法注入
方式:在类的构造方法上加上 @Autowired。Spring会在创建这个类实例时,通过调用这个构造方法来注入依赖。
代码示例:
@Controller
public class UserController2 {private UserService userService;@Autowired // 告诉Spring:请通过这个构造方法注入UserServicepublic UserController2(UserService userService) {this.userService = userService;}public void sayHi() {System.out.println("Hi, UserController2...");userService.sayHi();}
}
注意:
- 如果一个类只有一个构造方法,那么即使不加
@Autowired,Spring也会自动使用它进行注入(Spring 4.3+)。
[!TIP] 此时默认的无参构造方法不生效了,最好的习惯是补上默认的无参构造方法
Spring默认的是使用无参的构造方法(如有多个构造方法)
- 这样会导致之后在使用到
userService的地方有空指针报错异常(可以通过打印日志的方法来确定具体使用哪一个方法)
- 所以有多个构造方法的情况下,需要在需要的构造方法上加上
@Autowired注解表示指定使用哪一个
Setter 注入
方式:在Setter方法上加上 @Autowired。Spring会先创建对象,然后调用对应的Setter方法来注入依赖。
代码示例:
@Controller
public class UserController3 {private UserService userService;@Autowired // 告诉Spring:请通过这个Setter方法注入UserServicepublic void setUserService(UserService userService) {this.userService = userService;}public void sayHi() {System.out.println("Hi, UserController3...");userService.sayHi();}
}
[!QUESTION] 如果
setUserService方法上没有@Autowired,Spring还能注入吗?
不能。Spring需要@Autowired来知道这个方法是用来注入依赖的。
三种注入方式优缺点分析
#面经
| 方式 | 优点 | 缺点 |
|---|---|---|
| 属性注入 | 最简洁,使用方便。 | 1. 违反单一职责原则(SRP)。 2. 难以测试(只能用于IoC容器,并且在使用的时候才会出现空指针异常)。 3. 无法声明为 final。 |
| 构造方法注入 | 1. 依赖明确,强制依赖。 2. 可以声明为 final(推荐)。3. 保证对象完全初始化。因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法. 4. 更好的可测试性。 5. 通用性好,因为构造方法是JDK支持的,所以换任何框架都是使用的。 | 1. 依赖过多时,构造方法参数列表会很长。 |
| Setter 注入 | 1. 依赖可选(不强制)。 2. 可以在对象创建后进行配置。 | 1. 无法声明为 final。2. 可能会被多次调用,存在风险。 |
推荐:在大多数情况下,构造方法注入是最佳实践。它确保了依赖的不可变性,并使对象在创建时就处于完全可用的状态。
@Autowired 存在问题?
问题:如果Spring容器中有多个相同类型的Bean,@Autowired 会怎么做?
代码示例:
我们定义了两个 User 类型的Bean,名字分别是 user1 和 user2:
@Configuration
public class BeanConfig {@Bean("user1")public User user1() { /* ... */ }@Bean("user2")public User user2() { /* ... */ }
}
现在,我们在 UserController 中尝试注入 User:
@Controller
public class UserController {@Autowiredprivate UserService userService; // 假设只有一个UserService@Autowiredprivate User user; // 尝试注入Userpublic void sayHi() {System.out.println("Hi, UserController...");userService.sayHi();System.out.println(user); // 打印注入的User}
}
运行结果:
// 报错:NoUniqueBeanDefinitionException: No qualifying bean of type 'User' available: expected single matching bean but found 2
解释:Spring发现有两个 User 类型的Bean(user1 和 user2),它不知道该注入哪一个,所以报错了。
如何解决“多个相同类型Bean”的问题?
Spring提供了几种方式来解决歧义:
@Primary:优先注入。@Qualifier:指定Bean的名称。@Qualifier优先级比@Primary高
@Resource:按名称注入(JDK标准)。
@Primary
作用:当有多个相同类型的Bean时,给其中一个加上 @Primary,表示它是首选的。
代码示例:
@Configuration
public class BeanConfig {@Bean("user1")@Primary // 告诉Spring:当需要User类型时,优先注入我public User user1() { /* ... */ }@Bean("user2")public User user2() { /* ... */ }
}
现在,UserController 再次注入 User:
@Controller
public class UserController {@Autowiredprivate User user; // 会自动注入user1// ...
}
解释:当Spring需要注入 User 类型时,它会优先选择带有 @Primary 的 user1。
@Qualifier
作用:明确指定要注入哪个Bean的名称。
代码示例:
@Controller
public class UserController {@Autowired@Qualifier("user2") // 告诉Spring:我要注入名为“user2”的User Beanprivate User user;// ...
}
解释:@Qualifier 必须和 @Autowired 一起使用。它告诉Spring,即使有多个 User 类型的Bean,我只想要那个名字是 user2 的。
[!IMPORTANT] @Qualifier注解不能单独使⽤,必须配合@Autowired使⽤
@Resource
作用:@Resource 是JDK提供的注解,它默认是按名称进行注入。如果找不到同名的Bean,再尝试按类型注入。
代码示例:
@Controller
public class UserController {@Resource(name = "user1") // 告诉Spring:我要注入名为“user1”的Beanprivate User user;// ...
}
[[四种不同情况反应构造Spring框架中Bean的依赖注入和自动装配的不同规则]]
#面经
@Autowired vs @Resource 的区别:
@Autowired是spring提供的注解,@Resource是JDK提供的注解
@Autowired:Spring特有的,默认按类型注入。如果类型有多个,再尝试按名称匹配。@Resource:JDK标准,默认按名称注入。如果名称找不到,再尝试按类型匹配,并且其支持更多的参数设置,如name:根据名称获取Bean
Autowired装配顺序

