一、什么是 Spring IoC
1、概念
Spring 是包含众多工具方法的 IoC 容器。
- 包含众多工具方法:比如 Sring 核心包里 util 文件下的 StringUtil 类里的静态方法,等。
- 容器 :存、取东西的器皿。在这个场景下,存、取 的东西是对象(bean)。
- IoC:Inversion of Control(控制反转),一种思想。
Spring IoC 就是指:对象的生命周期的控制权,由对象的调用者掌握,反转为由 Spring 掌握。
2、传统程序开发
例如,设计汽车类:






但这种依赖,会导致:当汽车有更个性化的需求,比如加上颜色时,需要修改每个部件的参数列表:高耦合,一个模块的修改会导致其它模块一起修改。






3、IoC 程序开发
将车身、底盘、轮胎的创建外包出去,即:将每个对象的创建、销毁交给 Main 控制,不再由调用者控制:

java
package com.edu.springiocdemo.v2;
public class Car {
private Framework framework;
// size: 轮胎大小
public Car(Framework framework) {
this.framework = framework;
System.out.println("汽车已创建...");
}
public void run() {
System.out.println("汽车正在运行...");
}
}
package com.edu.springiocdemo.v2;
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
System.out.println("车身已创建");
}
}
package com.edu.springiocdemo.v2;
public class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
System.out.println("底盘已创建...");
}
}
package com.edu.springiocdemo.v2;
public class Tire {
private int size;
public Tire(int size) {
this.size = size;
System.out.println("轮胎尺寸:" + size);
}
}
增加颜色需求,只需要修改 Tire、Main 即可:



IoC 思想:将依赖类的控制权外包给别人,使用者只管使用,无需关心依赖类的实现细节。
优点:
- IoC 容器集中管理对象,方便后续存放、使用。(Spring 中,通过注解就能简单实现)
- 使用者无需了解创建对象的实现细节,降低了耦合度。
Spring IoC 就是 Spring 实现的 IoC。
二、什么是 Spring DI
1、概念
DI (Dependency Injection) 就是依赖注入,通俗讲,就是依赖赋值(把对象从容其中取出来使用)。而 Spring DI 就是 Spring 实现的 DI。
2、Spring IoC & DI 的使用例子
回到图书管理系统:对于 BookDao、BookService,每一次创建的对象的功能实际上是一样的,那我们只需要创建一个对象放到容器(@Component);使用时,从容其中取出对象(@AutoWired)。



三、IoC 详解
1、Bean 的存储
- 类注解:@Controller(控制层)、@Service(业务逻辑层)、@Repository(数据访问层)、@Component(组件)、@Configuration(配置层)。
- 方法注解:@Bean。
2、类注解(五大类注解)
五大类注解 ,在业务逻辑上的含义不同 ,分别管理不同层次的类,主要是为了分门别类提高可读性。但功能上 @Component 可以替代 @Service、@Repository、@Configuration (其余四个类注解,都是在 @Component 基础上延伸的),除了不能替代 @Controller(功能比 @Component 多一点,还负责接收用户传递的数据,所以我们必须在路由映射的类上加 @Component,表示这个接口类由 Spring 管理了。因此我们调用接口时也没有 new 对象)。
(1)Bean 的命名
- 默认情况:类名的小驼峰写法。
- 特殊情况(类名前两个字母是大写):保持类名不变,如 UController。
(2)按 Bean 类型获取 Bean
Spring Boot 项目的入口方法中,调用了一个 run 方法,它的返回类型与 "应用上下文" 有关:


应用上下文中包含了 Spring 管理的 Bean,我们通过它获得 Bean:

按 Bean 的类名获取:

如果没有加 @Controller,报错找不到 xxx 类型的 bean:

(3)按 Bean 名称获取 Bean
因为没有提供 Bean 类型,所以返回的 Bean 是 Object,需要强转:

如果 Bean 写错,会报错,找不到命名为 xxx 的 Bean:


(3)两者都有
先按类名找,找不到再按命名找。
可以看到,通过三种方法获取的 Bean,都指向同一个地址,因此,Spring 中的 IoC 容器应用了单例模式(对于某个类,只加了类注解):


只按类型获取 Bean 具有缺陷:**当一个类型有多个 Bean 时,无法获取。**在方法注解中详解。
3、方法注解(@Bean)
适用场景:
- 无法修改第三方类,加不了类注解。
- 需要为同一个类型创建多个 Bean。
命名规则:方法名。
使用 @Bean 创建两个 Bean:


如果依然使用按类型获取 Bean,则会因为同一类型的 Bean 有多个,而不知道返回哪个 Bean,产生报错:


某个类存在多个 Bean 时,需按命名获取:

4、修改 Bean 的默认命名
类注解:只能重命名一个命名。
方法注解:一个 Bean 可以有多个命名。

5、扫描路径(@ComponentScan)
由于整个项目中的代码很多(包括第三方库),所以如果启动类通过扫描所有路径下的文件,来知道哪些地方使用了类注解、哪些地方使用了方法注解,就会非常慢。因此,Spring 只会扫描跟 启动类同路径下的代码。



也可以修改扫描路径,修改后可以扫描到 controller 包下的 UserController.java:


四、DI 详解
IoC 的注解用来标识把哪些 Bean 交给 Spring 管理。DI 的注解就用来取出 Bean 使用。
1、属性注入
在属性上加 @AutoWired:
java
package com.edu.springiocdemo.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void hello() {
System.out.println("Hello from UserService");
}
}
package com.edu.springiocdemo.component;
import org.springframework.stereotype.Component;
@Component
public class UserComponenet {
public void hello() {
System.out.println("Hello from UserComponent");
}
}

运行:


2、构造方法注入
将要注入的 Bean 作为形参,并在构造方法上加 @AutoWired。
- 当只有一个 构造方法时,@AutoWired 可省略,类创建对象时执行该唯一构造方法。
- 当有多个 构造方法,且有无参构造方法时,@AutoWired 可省略 ,优先执行无参构造方法。
- 当有多个 构造方法,且没有无参构造方法 时,需通过 @AutoWired 指定优先执行哪个构造方法。
情况1:

情况2:如果不让无参构造优先,可在另一个有参构造方法上加 @AutoWired。

优先执行无参,bean 未被注入:

情况3:


修改:


3、Setter 注入
跟属性注入类似,set 方法上加 @AutoWierd:


4、三种注入的优缺点
(1)属性注入
优点:
- 使用简单,程序员最爱。
缺点:
- 不能注入被 final 修饰的属性。
- 只能用于 IoC 容器。
(2)构造方法注入(Spring 4.X 推荐,目前推荐)
优点:
- 可以注入被 final 修饰的属性。
- 注入对象不被修改(final 修饰)。
- 注入对象保证被完全初始化,因为构造函数是在类加载阶段就被执行的方法。
- 通用性好,由 JDK 提供,因此所有框架都能使用。
缺点:
- 注入多个对象时,构造函数代码比较复杂。
(3)Setter 注入(Spring 3.X 推荐,以前推荐)
优点:
- 方便在类实例后,重新对 bean 进行修改(再 set)。
缺点:
- 因为能反复调用 set,又有被修改的风险。
5、@AutoWired 存在的问题
(1)存在的问题
@AutoWired 默认按类注入(如果属性名是 bean 的命名,按照命名注入。如果 @Qualifier 指定 bean 命名,优先按照指定的命名注入)。
@AutoWired 默认按类注入,不知道注入哪个 bean。

UserComponent.java:

报错说可以使用这俩:

(2)解决办法1(@Qualifier)
如果属性名是 bean 的命名,按照命名注入:


如果 @Qualifier 指定 bean 命名,优先按照指定的命名注入:


(2)解决办法2(@Primary)
在优先的方法注解上加:

(3)解决办法3(@Resource,JDK 提供)
用 @Resource 指定命名:

(4)@AutoWired 和 @ReSource 的区别
- @AutoWired 是由 Spring 框架 提供的注解,@Resource 是由 JDK 提供的注解(可适用于任何框架)。
- @AutoWired 默认按类注入,@ReSource 默认按命名注入。
五、练习
将图书管理系统中的 Service 层、Dao 层的注解改成 @Servece、@Repository:


六、Spring、Spring Boot、Spring MVC 的区别和联系
它们都是开发应用框架。
(1)Spring:主要功能:管理对象、对象之间的依赖(IoC&DI);⾯向切⾯编程(AOP);数据库事务管理;web 框架支持(Spring MVC)等。突出优点:高度开放性,可自由选择 Spring 的部分或全部功能,也可接入第三方框架,如 MyBatis。
(2)Spring MVC:是一个 web 框架,是 Spring 的一个子框架。主要功能:开发 web 应用和网络接口(服务器与用户交互的部分,如 @RequestMapping)。
(3)Spring Boot:对 Spring 的一个封装,让开发更快速,让开发者专注于开发,而无需过多关注 XML 的依赖配置(以前都是通过 xml 自己写依赖关系,现在用注解就行。并且以前程序员必备技能就是排包,解决包的版本冲突,现在 Spring Boot 帮我们整理好了,从中央仓库找到坐标复制到 POM 文件就行)和底层实现。