文章目录
-
- 一、定义IoC&DI
-
- [1.1 Ioc](#1.1 Ioc)
-
- [1.1.1 传统程序开发](#1.1.1 传统程序开发)
- [1.1.2 IoC 程序开发](#1.1.2 IoC 程序开发)
- [1.1.3 IoC 优势](#1.1.3 IoC 优势)
- [1.2 DI](#1.2 DI)
- [二、IoC 详解](#二、IoC 详解)
-
- [2.1 Bean 的存储](#2.1 Bean 的存储)
- [2.2 @Controller (控制器存储)](#2.2 @Controller (控制器存储))
- [2.3 @Service(服务存储)](#2.3 @Service(服务存储))
- [2.4 @Repository (仓库存储)](#2.4 @Repository (仓库存储))
- [2.5 @Component(组件存储)](#2.5 @Component(组件存储))
- [2.6 @Configuration(配置存储)](#2.6 @Configuration(配置存储))
- [2.7 五大类注解区别](#2.7 五大类注解区别)
- [2.8 方法注解 @Bean](#2.8 方法注解 @Bean)
-
- [2.8.1 重命名 Bean](#2.8.1 重命名 Bean)
- [2.9 扫描路径](#2.9 扫描路径)
- [三、DI 详解](#三、DI 详解)
-
- [3.1 属性注入](#3.1 属性注入)
- [3.2 构造方法注入](#3.2 构造方法注入)
- [3.3 Setter 注入](#3.3 Setter 注入)
- [3.4 三种注入方式优缺点](#3.4 三种注入方式优缺点)
- [3.5 @Autowired 注解问题及解决](#3.5 @Autowired 注解问题及解决)
-
- [3.5.1 @Primary 解决](#3.5.1 @Primary 解决)
- [3.5.2 @Qualifier 解决](#3.5.2 @Qualifier 解决)
- [3.5.3 @Resource 解决](#3.5.3 @Resource 解决)
- [3.5.4 @Autowired 与 @Resource 区别](#3.5.4 @Autowired 与 @Resource 区别)
一、定义IoC&DI
1.1 Ioc
Spring 是包含了众多工具方法的IoC容器。IoC 是Spring的核心思想,例如前面章节所示:在类上面添加@RestController 和 @Controller 注解,,就是把这个对象交给Spring管理,Spring框架启动时就会加载该类。把对象交给Spring管理,就是IoC思想。
IoC :Inversion of Control (控制反转),也就是说Spring是一个"控制反转"的容器。控制反转也就是 控制权 反转。
获得依赖对象的过程被反转了,也就是说,当需要某个对象时,传统开发模式中需要自己通过new创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序中只需要依赖注入 (DependencyInjection,DI)就可以了。
这个容器称为:IoC容器。Spring是⼀个IoC容器,所以有时Spring也称为Spring容器。
下面进行 IoC 案例说明
1.1.1 传统程序开发
实现下面的需求:从右向左依次依赖。

按照每一个模块设计一个类,代码如下:
java
public class Car {
private Framework framework;
public Car(){
framework = new Framework();
System.out.println("car init...");
}
public void run() {
System.out.println("car run...");
}
}
public class Framework {
private Bottom bottom;
public Framework(){
bottom = new Bottom();
System.out.println("Framework init...");
}
}
public class Bottom {
private Tire tire;
public Bottom(){
tire = new Tire();
System.out.println("Bottom init...");
}
}
public class Tire {
private int size;
public Tire(){
this.size = 17;
System.out.println("size : " + size);
}
}
这样设计的可维护性太低,耦合度太高,当需求变更时,对上述代码修改起来会非常麻烦,当修改尺寸时,修改如下:

从上述得出,当底层代码改动后,整个链上的所有代码都需要进行修改。
- 解决方案
可以尝试不在每个类中自己创建下级类,如果自己创建下级类就会出现当下级类发生改变操作,
自己也要跟着修改。此时,我们只需要将原来由自己创建的下级类,改为传递的方式(也就是注入的方式),因为我们不需要在当前类中创建下级类了,所以下级类即使发生变化(创建或减少参数),当前类本身也无需修改任何代码,这样就完成了程序的解耦。
1.1.2 IoC 程序开发
基于上述方案,将程序进行改造,将创建子类的方式改为注入传递的方式,代码如下:
java
public class Main {
public static void main(String[] args) {
Tire tire = new Tire(17);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
}
public class Tire {
private int size;
public Tire(int size){
this.size = size;
System.out.println("size : " + size);
}
}
public class Bottom {
private Tire tire;
public Bottom(Tire tire){
this.tire = tire;
System.out.println("Bottom init...");
}
}
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom){
this.bottom = bottom;
System.out.println("Framework init...");
}
}
public class Car {
private Framework framework;
public Car(Framework framework){
this.framework = framework;
System.out.println("car init...");
}
public void run() {
System.out.println("car run...");
}
}
经过以上调整,无论底层类中如何变化,整个调用链不用做任何变化,就完成了代码之间的解耦合,使得更加灵活。这就是IoC思想。
1.1.3 IoC 优势
- 通过上述示例发现,通用程序的实现代码,类的创建顺序是反的,传统代码是Car控制并创建了 Framework,Framework创建并创建了Bottom,依次往下,而改进之后的控制权发生的反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入将当前对象中,依赖对象的控制权不再由当前类控制了。这样的话,即使依赖类发生任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是IoC的实现思想。
IoC容器也就是控制反转器。IoC 容器具有以下优点:
- 资源集中管理:IoC容器会帮我们管理⼀些资源(对象等),我们需要使用时,只需要从IoC容器中去取就可以了。
- 在创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,也就是耦合度。
1.2 DI
DI: DependencyInjection(依赖注入)
容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称之为依赖注入。
上述示例代码就是通过构造函数的方式,将依赖对象注入到需要使用的对象中的。
IoC 是一种思想,也是"目标",而思想只是一种指导原则,最终还是要有可行的落地方案,而DI就属于
具体的实现。所以也可以说,DI是IoC的一种实现。
二、IoC 详解
2.1 Bean 的存储
Spring 框架为了更好的服务Web应用程序,提供了更丰富的注解。
共有两类注解类型:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration
- 方法注解:@Bean
2.2 @Controller (控制器存储)
使用@Controller 注解存储Bean ,代码如下:
java
@Controller
public class UserController {
public void sayHello(){
System.out.println("do controller..");
}
}
加上注解后,此时这个对象就已经存储在Spring容器中了。
通过获取Spring上下文对象:发现成功获取到Controller对象
java
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserController bean = context.getBean(UserController.class);
System.out.println(bean);
}
}

- 获取Bean 对象的其他方式,ApplicationContext 也提供了其他方式进行获取:

可以根据名称来获取bean,那么bean 命名的规则是?
五大类注解让Spring管理Bean对象的默认取名方式如下:
- bean名称以小写字母开头,小驼峰命名。例:UserController, Bean的名称为:userController
- 当有多个字符并且第一个和第二个字符都是大写时,Bean对象名就是类名。例:UController, Bean的名称为:UController
2.3 @Service(服务存储)
使用@Service 注解存储bean代码如下,获取bean对象方式不变:
java
@Service
public class UserService {
public void doService(){
System.out.println("do service...");
}
}
2.4 @Repository (仓库存储)
使用@Repository 注解存储bean代码如下,获取bean对象方式不变:
java
@Repository
public class UserRepository {
public String doRepository(){
return "do repository...";
}
}
2.5 @Component(组件存储)
使用@Component 注解存储bean代码如下,获取bean对象方式不变:
java
@Component
public class UserComponent {
public String doComponent(){
return "do component...";
}
}
2.6 @Configuration(配置存储)
使用@Configuration 注解存储bean代码如下,获取bean对象方式不变:
java
@Configuration
public class UserConfig {
public String doConfig(){
return "do config...";
}
}
2.7 五大类注解区别
为什么要用这么多类注解呢,既然都是将对象交给Spring容器,使用哪个注解功能不是都一样吗,这样使用不同的注解是为了什么?
使用这么多类注解是为了应用的分层,当看到这些不同的注解,就能直接了解当前类的用途:
@Controller:控制层,接收请求,对请求进行处理,并进行响应.@Service:业务逻辑层,处理具体的业务逻辑.@Repository:数据持久层,也称为持久层.负责数据访问操作@Configuration:配置层.处理项目中的一些配置信息.
@Controller,@Service,@Repository,@Configuration这四个注解都是@Component注解的衍生注解,可以理解为是父子关系。
2.8 方法注解 @Bean
类注解是添加到某个类上,但是类注解存在问题:
- 使用外部包中类,没办法添加类注解
- 一个类,需要多个对象,无法使用类注解
此时需要使用方法注解 @Bean
使用示例:
java
@Configuration
public class BeanConfig {
@Bean
public UserInfo user1(){
return new UserInfo("zhangsan",17);
}
}
- 方法注解 @Bean 需要配合类注解才能将对象正常的存储到Spring容器中。
通过获取Spring上下文对象可以得到:
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserInfo bean1 = context.getBean("user1",UserInfo.class);
System.out.println(bean1);
}
}

2.8.1 重命名 Bean
可以通过设置name属性给Bean对象进行重命名操作,如下代码示例:
java
@Configuration
public class BeanConfig {
@Bean(name = {"u1","userInfo"})
public UserInfo user1(){
return new UserInfo("zhangsan",17);
}
}
此时可以使用 u1 获取UserInfo 对象:
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserInfo bean = (UserInfo) context.getBean("u1");
System.out.println(bean);
}
}

重命名代码也可以进行省略:@Bean({"u1","userInfo"}),@Bean("u1")
2.9 扫描路径
使用五大注解声明的Bean,生效必须配置扫描路径,让Spring扫描到这些注解,也就是通过 @ComponentScan 来配置这些路径。
但是默认扫描范围是SpringBoot 启动类所在的包及其子包,因此将启动类 SpringIocDemoApplication 放在希望扫描的包的路径下,那么定义的Bean 就都可以被扫描到。
三、DI 详解
依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象。简单来说,就是把对象取出来放到某个类的属性中。
关于依赖注入,Spring 提供了三种方式:
- 属性注入
- 构造方法注入
- Setter 注入
3.1 属性注入
属性注入使用 @Autowired 实现,示例:
java
@Service
public class UserService {
@Autowired
private UserInfo userInfo;
public void doService(){
System.out.println(userInfo);
System.out.println("do service...");
}
}
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserService bean = context.getBean(UserService.class);
bean.doService();
}
}

3.2 构造方法注入
构造方法注入是在类的构造方法中实现注入,示例:
java
@Controller
public class UserController {
//构造方法注入
private UserService userService;
public UserController(){
}
@Autowired
public UserController(UserService userService){
this.userService = userService;
}
public void sayHello(){
userService.doService();
System.out.println("do controller..");
}
}
- 当类中只有一个构造方法时,那么 @Autowired 注解可以省略;当类中有多个构造方法时,那么需要加上 @Autowired 明确指定使用哪个构造方法。只能在一个构造方法上加上 @Autowired 注解。
3.3 Setter 注入
Setter 注入和属性的 Setter 方法实现类似,只是在set方法时加上 @Autowired 注解,示例:
java
@Controller
public class UserController {
//Setter 注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHello(){
userService.doService();
System.out.println("do controller..");
}
}
3.4 三种注入方式优缺点
- 属性注入:
优点:简洁,使用方便
缺点:是能用于IoC 容器,并且只有在使用时才会出现异常;不能注入一个Final 修饰的属性。 - 构造函数注入
优点:可以注入Final 修饰的属性;注入的对象不会被修改;依赖对象在使用前一定会被完全初始化;通用性好,适用于所有框架。
缺点:注入多个对象时,代码比较繁琐,需要多个对象参数。 - Setter 注入
优点:方便在类示例之后重新对该对象进行配置或注入。
缺点:不能注入一个 Final 修饰的属性;注入对象可能会被改变,因为Setter 方法可能会被多次调用。
3.5 @Autowired 注解问题及解决
当同一类型存在多个bean时,使用@Autowired 会存在问题:
java
@Configuration
public class BeanConfig {
@Bean({"u1"})
public UserInfo user1(){
return new UserInfo("zhangsan",17);
}
@Bean({"u2"})
public UserInfo user2(){
return new UserInfo("lisi",20);
}
}
java
@Service
public class UserService {
@Autowired
private UserInfo userInfo;
public void doService(){
System.out.println(userInfo);
System.out.println("do service...");
}
}
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserService bean = context.getBean(UserService.class);
bean.doService();
}
}
此时,当有两个userInfo 对象时,获取上下文发现启动失败,信息如下:

报错原因是需要一个单一对象,但是找到了两个,因此Spring 提供了以下几种解决方案。
3.5.1 @Primary 解决
使用 @Primary 注解:当存在多个相同类型的Bean 注入时,加上 @Primary 注解,来确定默认Bean的实现。示例:
java
@Configuration
public class BeanConfig {
@Bean({"u1"})
public UserInfo user1(){
return new UserInfo("zhangsan",17);
}
@Bean({"u2"})
@Primary
public UserInfo user2(){
return new UserInfo("lisi",20);
}
}
此时默认获取到的对象就是 user2 。
3.5.2 @Qualifier 解决
使用@Qualifier 注解:指定当前要注入的Bean对象。在其 value 属性中,指定注入bean对象的名称。
- @Qualifier 注解不能单独使用,必须配合@Autowired 注解使用。
java
@Service
public class UserService {
@Autowired
@Qualifier("u1")
private UserInfo userInfo;
public void doService(){
System.out.println(userInfo);
System.out.println("do service...");
}
}
此时获取到对象为 user1。
3.5.3 @Resource 解决
使用@Resource 注解:是按照Bean 的名称进行注入。通过name属性指定要注入的bean 的名称。示例:
java
@Service
public class UserService {
@Resource(name = "u2")
private UserInfo userInfo;
public void doService(){
System.out.println(userInfo);
System.out.println("do service...");
}
}
此时获取到的对象为 user2。
3.5.4 @Autowired 与 @Resource 区别
- @Autowired 是Spring框架提供的注解,而 @Resource 是JDK 提供的注解。
- @Autowired 默认是按照类型注入,而@Resource 是按照名称注入。相比于@Autowired 来说,@Resource 支持更多的参数设置,例如name设置,根据名称获取Bean。