- 在简单了解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