Spring IoC&DI

发布于:2024-05-09 ⋅ 阅读:(22) ⋅ 点赞:(0)

一.引言

1.什么是Spring

前面我们说Spring是一个开源框架,它让我们的开发更为简单,它支持广泛的应用场景,有活跃庞大的社区。但这样说还是很抽象。今天我们就对他进行具象的描述

一句话说,Spring就是包含了众多工具的IoC容器。

Spring的两大核心思想就是IOC和AOP。

什么是容器?

容器就是盛放东西的,比如list/map是盛放数据的容器,tomcat是盛放web项目的容器,而Spring则是盛放对象的容器

什么是IOC?

IoC:Inverse of Control,控制反转。也就是说Spring是一个控制反转的容器。

什么是控制反转?即控制权的反转,控制权表示创建对象的控制权,反转指从代码/程序反转转移到Spring。

也就是说,当需要某一个对象时,或者说依赖于某个对象时,传统方式下java源代码就要进行new操作,但现在就不需要创建对象了,把创建对象的任务交给Spring来实现,想要哪个对象,直接从spring中取即可(也就是进行依赖注入DI)

2.IoC介绍

我们以造一辆车为例进行讲述

传统程序开发

汽车依赖于车身,车身依赖于底盘,地盘依赖于轮胎,所以有了下面几个类

但现在就会发现,tire尺寸是固定的,那要是我们想在造车时进行指定呢?那就是在new car时传入size参数,然后car的依赖framework在创建时也要传递这个参数,然后再framework的依赖bottom创建时还要传递这个参数,直到层层传递才能传递到tire那里,这就导致我们要修改好多出代码,如下:

这就出现了问题:当最底层代码进行修改后,整个调用链上的代码都得进行修改(其实不管是哪里修改,对应的调用链上的代码就得全部修改)

也就是说程序的耦合程度非常高

解决方案

在上面的开发中,汽车依赖车身,车身依赖底盘,底盘依赖轮胎;但我们要是将依赖关系转换过来:车身大小根据汽车整体大小进行选择,底盘大小根据车身大小进行选择,轮胎大小根据底盘大小进行选择;也就是底盘以来车身,轮胎依赖底盘

IoC开发

这样,要是想换一个轮胎大小,直接修改一处代码即可

也就完成了代码间的解耦合。

我们来和传统开发对比一下:在传统开发中,程序的入口是直接创建汽车,然后建车身,然后是底盘,最后是轮胎。而在IoC开发中,则是先创建好轮胎,轮胎准备好了再供底盘使用……

在传统开发中每个类里面对于依赖的对象都是主动new出来,而在ioc思想中,每个类里面以来的对象都是外部提供的

IoC开发的实际以及优点

在ioc开发中,下面这部分都是用Spring进行的

Spring创建这些对象,我们只需要使用即可,比如Spring创建了tire,我们只要在CarBottom中使用即可。Spring创建了CarBottom,我们只要在carFramework中使用即可

优点:

资源不由使用资源的各方进行管理,而由不使用资源的其他一方进行管理。首先,资源集中管理,实现了资源的可配置和易管理;其次,降低了使用资源的各方的依赖程度,也就是解耦合。

Spring就是一种IoC容器,帮助我们进行资源管理

3.DI介绍

DI即Dependency Injection,依赖注入

容器在运行过程中,动态的为应用程序提供运行时所依赖的资源,就是依赖注入

二.IOC DI的使用

Spring是一个容器,容器就有两个功能,一个就是存,另一个就是取

Spring存取的是对象,我们将这些对象称之为Bean。我们只需要通过程序,告知Spring哪些对象要进行管理,也就是告诉Spring哪些对象要进行存,以及如何从Spring中取出对象

1.存对象

bean的存储是通过注解实现的,可以分为两类:

1.类注解(五大注解):@Controller @Service @Component @Repository @Configuration

2.方法注解:@Bean

五大注解/类注解

@Controller(控制器存储)

那如何执行到这个类中的方法呢?如何观察到这个对象已经存在于Spring容器中呢?

先看一下Spring的启动类(加了@SpringBootApplication注解的就是启动类):

这里面自带一个run方法,他的返回值就是Spring容器,我们应该用ApplicationContext来接收

ApplicationContext常被称为Spring上下文,其实应该理解为应用环境,也就是Spring运行的环境

 ctx中存放到就是受到Spring管理的对象,然后我们通过getBean方法传入对象的类型来获取到对象。但要是将UserController类上的@Controller给删除掉,就会报错没有bean的定义

bean对象的其他获取方式:

进入到getBean方法,我们会发现这个方法是BeanFactroy这个接口的,而不是ApplicationContext这个接口的,因为ApplicationContext继承了BeanFactory

它里面有多个重载方法,常用的就是以下三个

name指的就是Bean的名称。当将对象交给Spring进行管理时,如果没有指定bean的名字,那么Spring就会给它一个默认的名字。一般来说就是类名的首字母小写。但当类名的前两个字母都是大写时,bean的名称就是类名本身

示例如下:

@Service(服务存储)

和@Controller的使用方法一模一样

@Component(组件存储)
@Configuration(配置存储)
@Repository(仓库存储)

以上五大注解的使用方法完全一样,只要加在类上面即可。

为什么要这么多类注解?

这和前面提到的应用分层相呼应:让程序员看到类注解之后,就能直接了解到当前类的用途

@Controller:控制层,接收请求,对请求进行处理,并进行响应

@Service:业务逻辑层,处理具体的业务逻辑

@Repository:数据访问层,也称为持久层,负责数据访问操作

@Configuration:配置曾,处理项目中的一些配置信息

这样有了分工,整个调用流程就有了框架,前端调用@Controller层代码,发送请求。@Controller通过调用@Service层代码帮助完成响应的形成,而@Service层又会通过调用@Repository层代码完成数据访问,进而完成整体业务处理

类注解之间的关系

查看@Controller/@Service/@Repository/@Configuration注解的源码,发现它们里面都有一个注解@Component,说明它们都是@Component的子类,是他的衍生注解。@Component就是一个元注解,也就是说注解其他类注解

方法注解@Bean

五大注解存在问题:

1.五大注解只能加在类上,并且是自己写的代码上,但是要是使用外部包(第三方包)里面的类,就没有办法添加类注解了

2.一个类可能需要多个对象,比如多个数据源,但是通过类注解反复获取对象其实获取到的是同一个对象,如下:

返回结果是true

这时就要转换使用@Bean方法注解。我们先来看看方法注解如何使用:创建一个config(表示配置,在这个例子里就是Spring创建对象,对对象进行配置)包,UserInfo类

再写一个BeanConfig类

然后尝试获取对象

报错啦!

没有bean定义

方法注解要搭配类注解

Spring对对象的管理不是说有个@Bean就会去创建对象,要想扫描到@Bean,首先要能够让Spring扫描到@Bean修饰的方法所在的类,所以要给BeanConfig类加上类注解

这样就能够扫描到啦

定义多个对象

这回又报错啦:

他说没有唯一的对象,找到啦两个

所以当定义了多个对象时,就不能仅仅使用类来进行获取,而应该是用bean名称进行获取。那么Bean的名称是啥?

由报错信息可以分析出,bean的名称就是方法名!!!即userInfo1,userInfo2

重命名bean

可以通过设置name属性进行命名

这两种方法都可以

如果只有一个名字,可以不加{}

Spring扫描路径

使用前面学习的五大注解声明的Bean一定会生效吗?不一定

因为要想生效,就要让该类被Spring扫描到。下面我们将启动类放到carController包下面,而BeanConfig还在config包下面

这次就报错了

这是因为,Spring没有扫描到BeanConfig类,要想被扫描到,就要加上@ComponentScan注解

那为啥之前不加就可以?

之前@ComponentScan虽然没有显式配置,但是@SpringBootApplication中已经包含了。并且默认扫描路径是SpringBoot启动类所在包及其子包

更为推荐到做法是将启动类放到我们期望扫描到的包底下

2.取对象——DI详解

上面我们将来控制反转的细节,现在我们来讲一讲依赖注入的细节

依赖注入表示:IoC容器在创建Bean时,去提供运行时所依赖的资源,而这里的资源指的就是对象。

简单来说就是把对象给取出来,放到某个类的属性中

关于依赖注入,有三个方法

属性注入:

属性注入是使用@Autowired注解完成。例如

实际上就是Spring已经通过@Service这个注解将UserService这个类的对象创建出来了,然后在构造UserController对象时,通过@Autowired注解将已经存在的Service对象取出来赋值给Controller对象

构造方法注入:

直接对该类加上一个构造方法即可

但注意:如果这个类只有一个构造方法,并且对成员变量进行了赋值,那么后续调用func方法就不会报错。但如果有多个构造方法,就会报错:

第一种情况是一个有参构造,一个无参构造,那么Spring就会默认使用无参构造,那么在调用func方法时就会出现空指针异常

第二种情况是两个有参构造,没有无参构造,那么就会连编译都不会通过,因为如下:

上面写了不知道要匹配哪个构造方法。所以这时候就应该通过@Autowired、注解来指定要使用哪个构造方法

Setter注入:

在Setter方法上要加上@Autowired注解

三种注入的优缺点

属性注入

优点:简洁

缺点:只能用于IoC容器,非IoC容器不可用;并且无法注入一个final修饰的属性。final修饰的属性值不可变,要么在定义时赋值,要么在构造方法中赋值

构造方法注入

优点:可以注入final修饰的属性;注入的对象不会被修改;依赖对象会在使用前完全被初始化,因为依赖对象是在类的构造方法中执行,而构造方法会在类加载阶段执行

缺点:当yilai对象多时,代码冗余

Setter方法注入

优点:方便在类实例后,重新对对象进行配置或赋值

缺点:不能注入final属性,注入对象可能会被改变,因为setter方法可能会被多次调用

@Autowired存在的问题

当同一个类型存在多个Bean对象时,简单使用@Autowried就会出现问题

报错的原因是,非唯一的Bean对象

如何解决上述问题

修该成员变量的名称

这个名称和其中一个Bean的名称相同

@Primary

使用@Primary注解,确认默认的实现使哪个对象

@Qualifier

在@Qualifier中指定要使用的Bean对象的名称。这个注解不能单独使用,只能搭配@Autowried注解使用

@Resource

这个注解表示按照Bean的名称进行注入。不用搭配@Autowired注解

@Resource和@Autowried的区别:

@Autowried是Spring框架提供的注解。@Resource是JDK提供的

@Autowried默认是按照类型进行注入,@Resource是按照名称进行注入

@Resource支持更多参数设置,例如name设置,从而根据名称获取Bean