一.引言
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