一、Spring IoC&DI
1. Spring 是什么?
通过前面的学习,我们知道了Spring是一个开源框架,他让我们的开发更加简单,他支持广泛的应用场景,有着活跃而庞大的社区,这也是Spring能够长久不衰的原因,但是这个概念相对来说,还是比较抽象
spring中的核心思想是IoC和AOP
我们用一句更具体的话来概括Spring,那就是: Spring 是包含了众多工具方法的 IoC 容器
1. 什么是容器?
生活中的水杯,垃圾桶,冰箱等等这些都是容器
在编程语言中,Map,List等都是容器
2. 什么是 IoC?
IoC 是Spring的核心思想,也是常见的面试题,那什么是IoC呢?
其实IoC我们在前面已经使用了,我们在前面讲到,在类上面添加 @RestController 和
@Controller 注解,就是把这个对象交给Spring管理,Spring 框架启动时就会加载该类,把对象交给Spring管理,就是IoC思想
IoC:Inversion of Control (控制反转),也就是说 Spring 是一个控制反转的容器
什么是控制反转呢?也就是控制权反转,什么的控制权发生了反转?获得依赖对象的过程被反转了,也就是说,当需要某个对象时,传统开发模式中需要自己通过 new 创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序中只需要依赖注入 (Dependency Injection,DI)就可以了,这个容器称为:IoC容器,Spring是一个IoC容器,所以有时Spring 也称为Spring 容器
2. IoC 介绍
接下来我们通过案例来了解一下什么是IoC
需求:造一辆车
1. 传统程序开发
先设计轮子(Tire),然后根据轮子的大小设计底盘(Bottom),接着根据底盘设计车身(Framework),最后根据车身设计好整个汽车(Car),这里就出现了一个依赖关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子
java
public class Main {
public static void main(String[] args) {
Car car = new Car(20);
car.run();
}
}
public class Car {
private Framework framework;
public Car(int size) {
this.framework = new Framework(size);
System.out.println("car init");
}
public void run(){
System.out.println("car run");
}
}
public class Framework {
private Bottom bottom;
public Framework(int size){
this.bottom = new Bottom(size);
System.out.println("framework init");
}
}
public class Bottom {
private Tire tire;
public Bottom(int size){
this.tire = new Tire(size);
System.out.println("bottom init");
}
}
public class Tire {
private int size;
public Tire(int size) {
this.size = size;
System.out.println("tire init, size=" + size);
}
}
2. 问题分析
这样的设计看起来没问题,但是可维护性却很低
接下来需求有了变更:随着对的车的需求量越来越大,个性化需求也会越来越多,我们需要加工多种尺寸的轮胎,那么由于依赖性,Tire之前的代码都要就行改动,耦合性高
3. 解决方案
我们尝试换一种思路,我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子,这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身,车身依赖汽车
基于以上思路,我们把调用汽车的程序示例改造一下,把创建子类的方式,改为注入传递的方式
java
public class Main {
public static void main(String[] args) {
Tire tire = new Tire(20, "red");
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
}
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");
}
}
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
System.out.println("framework init");
}
}
public class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
System.out.println("bottom init");
}
}
public class Tire {
private int size;
public Tire(int size, String color) {
this.size = size;
System.out.println("tire init,siz="+size);
}
}
代码经过以上调整,无论底层类如何变化,整个调用链是不用做任何改变的,这样就完成了代码之间的解耦,从而实现了更加灵活、通用的程序设计了
4. IoC 优势
在传统的代码中对象创建顺序是:Car -> Framework -> Bottom -> Tire
改进之后解耦的代码的对象创建顺序是:Tire -> Bottom -> Framework -> Car
我们发现了一个规律,通用程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了Framework,Framework 创建并创建了 Bottom,依次往下,而改进之后的控制权发生的反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入将当前对象中,依赖对象的控制权不再由当前类控制了,这样的话,即使依赖类发生任何改变,当前类都是不受影响的,这就是典型的控制反转 ,也就是 IoC 的实现思想
IoC容器的优点:
解耦: 您的业务逻辑代码不再需要关心对象的实例化和依赖查找,只专注于业务实现
可维护性高: 依赖关系集中在配置中,修改依赖时不需要修改源代码
3. DI 介绍
DI: Dependency Injection(依赖注入)
容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称之为依赖注入
程序运行时需要某个资源,此时容器就为其提供这个资源
从这点来看,依赖注入(DI)和控制反转(IoC)是从不同的角度的描述的同一件事情,依赖注入是从应用程序的角度来描述,就是指通过引入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦
上述代码中,是通过构造函数的方式,把依赖对象注入到需要使用的对象中的
IoC 是一种思想,也是目标, 而思想只是一种指导原则,最终还是要有可行的落地方案,而 DI 就属于具体的实现。所以也可以说,DI 是IoC的一种实现
二、 IoC & DI 使用
既然 Spring 是一个 IoC(控制反转)容器,作为容器,那么它就具备两个最基础的功能: 存和取
Spring 容器 管理的主要是对象,这些对象,我们称之为Bean 我们把这些对象交由Spring管理,由Spring来负责对象的创建和销毁。我们程序只需要告诉Spring,哪些需要存,以及如何从Spring中取出对象
目标:把BookDao,BookService 交给Spring管理,完成Controller层、Service层、Dao层的解耦
步骤:
- Service层及Dao层的实现类,交给Spring管理: 使用注解: @Component
- 在Controller层 和Service层 注入运行时依赖的对象: 使用注解 @Autowired
java
@Component
public class BookDao {
public List<BookInfo> mockData(){
List<BookInfo> bookInfos = new ArrayList<>();
for (int i=1; i<=15; i++){
BookInfo bookInfo = new BookInfo();
bookInfo.setId(i);
bookInfo.setBookName("图书"+ i);
bookInfo.setBookAuthor("作者"+i);
bookInfo.setCount(new Random().nextInt(100));
bookInfo.setPrice(new BigDecimal(new Random().nextInt(100)));
bookInfo.setPublish("出版社"+i);
bookInfo.setStatus(i%5==0?2:1);
bookInfos.add(bookInfo);
}
return bookInfos;
}
}
@Component
public class BookService {
@Autowired
private BookDao bookDao;
public List<BookInfo> getList(){
// BookDao bookDao = new BookDao();
List<BookInfo> bookInfos = bookDao.mockData();
//处理图书状态
for (BookInfo bookInfo:bookInfos){
//此处只考虑1和2两种情况
if (bookInfo.getStatus()==1){
bookInfo.setStatusCN("可借阅");
}else {
bookInfo.setStatusCN("不可借阅");
}
}
return bookInfos;
}
}
@RestController
@RequestMapping("/book")
public class BookController {
@Autowired
BookService bookService;
@RequestMapping("/getList")
public List<BookInfo> getList() {
//查询图书信息, 并返回
// BookService bookService = new BookService();
List<BookInfo> bookInfos = bookService.getList();
return bookInfos;
}
}
三、IoC 详解
通过上面的案例,我们已经知道了Spring IoC 和DI的基本操作,接下来我们来系统的学习Spring IoC和DI的操作
前面我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。也就是bean的存储
1. Bean的存储
Spring框架为了更好的服务web应用程序,提供了丰富的注解
共有两类注解类型可以实现:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
- 方法注解:@Bean
@Controller(控制器存储)
java
public class UserContraller {
public String sayHello() {
return "hello";
}
}

获取bean的三种方式


@Service(服务存储)
java
@Service
public class UserService {
public String doservice() {
return "do service...";
}
}

@Repository(仓库存储)
java
@Repository
public class UserRepository {
public String dorepository() {
return "dorepository";
}
}
@Component(组件存储)
java
@Component
public class UesrComponent {
public String doComponent() {
return "doComponent";
}
}
@Configuration(配置存储)
java
@Configuration
public class UesrConfig {
public String doconfig() {
return "doconfig";
}
}
2. 为什么要这么多类注解?
这个也是和咱们前面讲的应用分层是呼应的,让程序员看到类注解之后,就能直接了解当前类的用途
• @Controller:表现层,接收请求,对请求进行处理,并进行响应
• @Servie:业务逻辑层,处理具体的业务逻辑
• @Repository:数据访问层,也称为持久层,负责数据访问操作
• @Configuration:配置层,处理项目中的一些配置信息
类注解之间的关系
其实这些注解里面都有一个注解 @Component ,说明它们本身就是属@Component 的"子类"。@Component 是一个元注解,也就是说可以注解其他类注解,如 @Controller,@Service ,@Repository 等. 这些注解被称为 @Component 的衍生注解
3. 方法注解 @Bean
类注解是添加到某个类上的, 但是存在两个问题:
- 使用外部包里的类, 没办法添加类注解
- 一个类, 需要多个对象, 比如多个数据源
这种场景, 我们就需要使用方法注解 @Bean
1. 方法注解要配合类注解使用
java
@ComponentScan(basePackages = "com.bit.ioc")
@Configuration
public class BeanConfig {
@Bean({"u2", "userInfo2"})
public UserInfo userInfo2(){
return new UserInfo("lisi", 17);
}
}
java
UserInfo bean2 = context.getBean(UserInfo.class);
System.out.println(bean2);

2. 定义多个对象
java
@Configuration
public class BeanConfig {
@Bean
public UserInfo userInfo(){
return new UserInfo("zhangsan", 16);
}
@Bean
public UserInfo userInfo2(){
return new UserInfo("lisi", 17);
}
}
java
UserInfo bean2 = context.getBean(UserInfo.class);
System.out.println(bean2);

报错信息显示:期望只有一个匹配,结果发现了两个,userInfo,userInfo2
从报错信息中,可以看出来,@Bean 注解的bean,bean的名称就是它的方法名
接下来我们根据名称来获取bean对象
java
UserInfo bean1 = (UserInfo) context.getBean("userInfo");
System.out.println(bean1);
UserInfo bean2 = (UserInfo) context.getBean("userInfo2");
System.out.println(bean2);

3. 重命名 Bean
可以通过设置 name 属性给 Bean 对象进行重命名操作
java
@Bean({"u2", "userInfo2"})
public UserInfo userInfo2(){
return new UserInfo("lisi", 17);
}
java
UserInfo bean1 = context.getBean("u2", UserInfo.class);
System.out.println(bean1);
UserInfo bean2 = context.getBean("userInfo2", UserInfo.class);
System.out.println(bean2);

4. 扫描路径
使用前面学习的四个注解声明的bean,一定会生效吗?
不一定,bean想要生效,还需要被Spring扫描



加上@ComponentScan(basePackages = "com.flower.ioc")


在配置类上添加 @ComponentScan 注解,该注解默认会扫描启动类所在的包下所有的配置类
把启动类放在我们希望扫描的包的路径下,这样我们定义的bean就都可以被扫描到
四、 DI 详解
依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象,我们使用 @Autowired 这个注解,完成依赖注入的操作,简单来说,就是把对象取出来放到某个类的属性中
关于依赖注入,Spring也给我们提供了三种方式:
- 属性注入
- 构造方法注入
- Setter 注入
1. 属性注入
属性注入是使用 @Autowired 实现的,将 Service 类注入到 Controller 类中
java
//属性注入
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
public void sayHello(){
userService.doService();
userRepository.doRepository();
System.out.println("do controller");
}
java
UserController bean = context.getBean(UserController.class);
bean.sayHello();

2. 构造方法注入
java
private UserService userService;
private UserRepository userRepository;
public UserController() {
System.out.println("执行UserController 无参构造方法....");
}
public UserController(UserService userService) {
System.out.println("执行UserController 有参构造方法1....");
this.userService = userService;
}
public UserController(UserRepository userRepository) {
System.out.println("执行UserController 有参构造方法3....");
this.userRepository = userRepository;
}
@Autowired
public UserController(UserService userService, UserRepository userRepository) {
System.out.println("执行UserController 有参构造方法2....");
this.userService = userService;
this.userRepository = userRepository;
}

若有一个构造方法,则执行该构造方法
若有多个构造方法,必须要有无参构造,否则报错,通过 @Autowired 明确指定到底使用哪个构造方法
3. Setter 注入
java
private UserService userService;
private UserRepository userRepository;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}

4. 三种注入优缺点分析
属性注入
优点: 简洁,使用方便;
缺点:
1.只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现空指针异常
2.不能注入一个Final修饰的属性
构造函数注入
优点:
1.可以注入final修饰的属性
2.注入的对象不会被修改
3.依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法
4.通用性好,构造方法是JDK支持的,所以更换任何框架,他都是适用的
缺点:
注入多个对象时,代码会比较繁琐
Setter注入
优点: 方便在类实例之后,重新对该对象进行配置或者注入
缺点:
1.不能注入一个Final修饰的属性
2.注入对象可能会被改变,因为setter方法可能会被多次调用, 就有被修改的风险
5. @Autowired存在问题
当同一类型存在多个bean时,使用 @Autowired 会存在问题
java
@Bean
public UserInfo userInfo(){
return new UserInfo("zhangsan", 16);
}
@Bean({"u2", "userInfo2"})
public UserInfo userInfo2(){
return new UserInfo("lisi", 17);
}
java
@Autowired
private UserInfo u1;
public void doService() {
System.out.println(u1);
System.out.println("doService");
}

报错的原因是,非唯一的 Bean 对象
如何解决上述问题呢?Spring提供了以下几种解决方案
@Primary
@Qualifier
@Resource
1. @Primary
java
@Bean({"u2", "userInfo2"})
@Primary
public UserInfo userInfo2(){
return new UserInfo("lisi", 17);
}

2. @Qualifier
java
@Autowired
@Qualifier("u2")
// @Resource(name = "u2")
private UserInfo u1;
public void doService() {
System.out.println(u1);
System.out.println("doService");
}

3. @Resource
java
@Resource(name = "u2")
private UserInfo u1;
public void doService() {
System.out.println(u1);
System.out.println("doService");
}

4. 若将引入的u1改为方法名,结果会如何呢?
java
@Autowired
private UserInfo userInfo;
public void doService() {
System.out.println(userInfo);
System.out.println("doService");
}

java
@Autowired
private UserInfo userInfo2;
public void doService() {
System.out.println(userInfo2);
System.out.println("doService");
}

由此可见,方法名也可以避免冲突
常见面试题:
@Autowird 与 @Resource的区别
@Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
@Autowired 默认是按照类型注入,而@Resource是按照名称注入. 相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean
五、总结
1. Spring, Spring Boot 和Spring MVC的关系以及区别
Spring :简单来说,Spring 是一个开发应用框架,什么样的框架呢,有这么几个标签:轻量级、一站式、模块化,其目的是用于简化企业级应用程序开发
Spring MVC :Spring MVC是Spring的一个子框架,Spring诞生之后,大家觉得很好用,于是按照MVC模式设计了一个 MVC框架(一些用Spring 解耦的组件),主要用于开发WEB应用和网络接口,所以,Spring MVC 是一个Web框架
Spring Boot : Spring Boot是对Spring的一个封装,为了简化Spring应用的开发而出现的,中小型企业,没有成本研究自己的框架,使用Spring Boot 可以更加快速的搭建框架,降级开发成本,让开发人员更加专注于Spring应用的开发,而无需过多关注XML的配置和一些底层的实现

当你创建一个现代的 Java Web 项目(使用 Spring Boot)时,实际发生的流程是:
Spring Boot 启动,扫描项目依赖。
它发现你有 spring-boot-starter-web,于是自动配置好 Spring MVC(配置 DispatcherServlet)
它初始化 Spring Framework 的容器(ApplicationContext),开始管理你的 Bean。
它启动内置的 Tomcat 服务器,监听 8080 端口
当请求进来时,Spring MVC 的 Controller 接收请求,调用由 Spring 管理的 Service 进行业务处理
Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的一个
MVC 框架,而Spring Boot 是基于Spring的一套快速开发整合包
2. bean 的命名
- 五大注解存储的bean
前两位字母均为大写, bean名称为类名
其他的为类名首字母小写
通过 value属性设置 @Controller(value = "user") - @Bean 注解存储的bean
bean名称为方法名
通过name属性设置 @Bean(name = {"u2","userInfo2"})
3. 常见面试题
- 三种注入方式的优缺点(参考上面)
- 常见注解有哪些? 分别是什么作用?
web url映射: @RequestMapping
参数接收和接口响应: @RequestParam, @RequestBody, @ResponseBody
bean的存储: @Controller, @Service, @Repository, @Component, @Configuration,
@Bean
bean的获取: @Autowired, @Qualifier, @Resource - @Autowired 和 @Resource 区别
- 说下你对Spring,SpringMVC,Springboot的理解(参考上面)
本期内容到此为止,喜欢的话请点个赞,谢谢观看!!!