(一).前言
在正式介绍IoC之前,我们先要了解一下什么是Spring,这里说的"Spring"指的是"Spring Core"
Spring 是一个包含了众多工具方法的IoC容器 。"容器"其实不难理解,之前在介绍数据结构的时候,List,Map都是容器。所以说"容器"就可以理解为装东西的。那么这里装的是什么?答:装的是**"对象"**。
对于Spring Core来说,两大核心思想就是IoC 和 AOP。IoC我们马上就介绍,AOP这个后面再介绍。
1.概念
IoC (Inversion of Control),控制反转,本质是对象的控制权发生反转 。 传统开发中,我们需要自己在代码里new创建对象、手动实例化类、手动组装类之间的依赖,对象的创建与管理控制权在我们业务代码手中。 而 Spring IoC 容器统一管理所有 Java 对象(Bean),把对象创建、销毁、依赖装配的控制权从业务代码转移到容器,这就是**"控制反转"**。
就拿上一篇博客中的图书管理系统的那个案例来说,当时我们程序运行的时候,也不用我们自己手动 new 对象,然后我们输入了"用户名"和"密码"之后,也可以进行校验是否正确。如果我们不用Spring,则需要自己new一个对象,然后通过对象的引用调用login()方法;当使用了Spring之后,Spring 会提前创建好对象,我们直接使用注入(DI,后面介绍)好的对象调用login即可
2.案例
我们会将之前的那个图书管理系统就行修改,修改成IoC和DI的思想。
注意:我们这里先使用,再介绍。先用起来,然后下面给大家介绍
这里,我们使用到了两个注解,一个@Component,一个是@Autowired。
@Component 表示 把对象交给Spring管理
@Autowired 表示 从Spring中获取对象,并给属性赋值

可以看到,在修改之前,我们都是通过new对象的方式。对象的控制权在"引用"手里控制着。那么现在我们根据"IoC"的思想来修改我们的代码

对于,上图中代码。让Spring 对象的过程,就称为"IoC",即不需要再new对象了,而是由Spring 进行创建,然后由Spring进行管理;从Spring中获取对象的过程,就称为"DI",即"依赖注入",直接从Spring容器中获取对象

发现,是可以正常运行的,下面就开始细致的介绍Spring IoC 和Spring DI 了
(二).Spring IoC
1.概念
IoC,"控制反转",就是将对象(Bean)的控制权交给了Spring的IoC容器,由IoC容器创建和管理对象,也就是Bean的存储。
2.Bean的存储
(1).介绍注解
Bean,即"对象"。在上面的图书管理系统案例中,我们把某个对象交给IoC容器管理,需要在类上添加一个@Component注解。事实上,Spring为了提供更好的web应用程序,提供了更多的注解
这里分为两类:
Ⅰ.类注解:①.@Controller ②.@Component ③.@Configuration ④.Service ⑤.Repository
Ⅱ.方法注解:①.@Bean
(2).介绍上下文

上图,是Spring启动类的代码,事实上,这个类也是一个普通的类,之所以是"启动类"的原因,是因为加了"@SpringBootApplication"这个注解
下面看这个run()方法,这个启动类调用了run()方法

ApplicationContext,可以理解为"上下文"。所谓"上下文"就可以理解为"容器",就是用来装一些东西的。ApplicationContext就可以理解为"Spring运行环境",当启动整个类的时候,此时Spring就会帮我们管理一些东西,我们的对象,就在ApplicationContext中放着。此时,我们先需要接收run()方法的接收结果,然后在结果集中获取对象

(3).使用类注解获取对象
获取对象,我们使用的方法是getBean()方法


这个"名称"就类似于"学号",是唯一的
关于"名称",通过官方文档来看

Ⅰ.@Controller注解

java
public static void main(String[] args) {
ApplicationContext applicationContext=SpringApplication.run(SpringIoCBlogApplication.class, args);
HelloController bean1 = applicationContext.getBean(HelloController.class); //通过类名获取
HelloController bean2 = (HelloController) applicationContext.getBean("helloController");
HelloController bean3 = applicationContext.getBean(HelloController.class, "helloController");
bean1.print();
bean2.print();
bean3.print();
System.out.println(bean1);
System.out.println(bean2);
System.out.println(bean3);
}

可以看到,获取的对象是同一个对象
注意:通过类名获取对象的这种方式,只适合该类型的对象只有一个,至于为什么,下面介绍

可以看到,当类名的前两个字母是大写的时候,对象的名称就为类名

我们还可以对类型进行重命名,也是可以访问成功的

可以看到,当我将@Controller注解,注释掉,再运行程序,发现报错了,因为Spring没有找到一个类型为"HelloController"的对象。主要的原因还是因为我们将@Controller注解注释掉的原因。没被注释之前,我们让Spring帮我们创建一个HelloController的对象,但是当我们将@Controller注解注释掉后,Spring不会再帮助我们创建一个HelloController的对象了,所以才会报错

这个报错的原因虽然和上一个报错的原因不一样,但是本质上都是一样的,都是因为我们将"@Controller"注释掉的原因。下面再介绍其他注解的时候也是同样的效果,所以下面就不演示报错信息了
Ⅱ.@Configuration注解
java
public static void main(String[] args) {
ApplicationContext applicationContext=SpringApplication.run(SpringIoCBlogApplication.class, args);
HelloConfiguration bean1 = applicationContext.getBean(HelloConfiguration.class);
HelloConfiguration bean2 = (HelloConfiguration) applicationContext.getBean("helloConfiguration");
HelloConfiguration bean3 = applicationContext.getBean(HelloConfiguration.class, "helloConfiguraiton");
bean1.print();
bean2.print();
bean3.print();
System.out.println(bean1);
System.out.println(bean2);
System.out.println(bean3);
}

Ⅲ.@Controller注解
java
public static void main(String[] args) {
ApplicationContext applicationContext=SpringApplication.run(SpringIoCBlogApplication.class, args);
HelloComponent bean1 = applicationContext.getBean(HelloComponent.class);
HelloComponent bean2 = (HelloComponent) applicationContext.getBean("helloComponent");
HelloComponent bean3 = applicationContext.getBean(HelloComponent.class, "helloComponent");
bean1.print();
bean2.print();
bean3.print();
System.out.println(bean1);
System.out.println(bean2);
System.out.println(bean3);
}

Ⅳ.@Service注解
java
public static void main(String[] args) {
ApplicationContext applicationContext=SpringApplication.run(SpringIoCBlogApplication.class, args);
HelloService bean1 = applicationContext.getBean(HelloService.class);
HelloService bean2 = (HelloService) applicationContext.getBean("helloService");
HelloService bean3 = applicationContext.getBean(HelloService.class, "helloService");
bean1.print();
bean2.print();
bean3.print();
System.out.println(bean1);
System.out.println(bean2);
System.out.println(bean3);
}

Ⅴ.@Repository注解
java
public static void main(String[] args) {
ApplicationContext applicationContext=SpringApplication.run(SpringIoCBlogApplication.class, args);
HelloRepository bean1 = applicationContext.getBean(HelloRepository.class);
HelloRepository bean2 = (HelloRepository) applicationContext.getBean("helloRepository");
HelloRepository bean3 = applicationContext.getBean(HelloRepository.class,"helloRepository");
bean1.print();
bean2.print();
bean3.print();
System.out.println(bean1);
System.out.println(bean2);
System.out.println(bean3);
}

(4).类注解这么多的原因

通过上面的图片可以看到,@Controller,@Configuration,@Repository,@Service 这四个注解里面都有同一个"注解@Component",可以理解为"@Component"注解,是其他四个注解的"衍生注解",这四个注解之所以能让Spring管理对象,本质上还是"@Component"注解发挥着作用。
但是,这四个注解也是有区别的,最主要的区别就是"分层"
@Controller表示的是"控制层",主要用于接受请求,并且进行响应
@Service表示的是"业务逻辑层",处理具体的业务逻辑
@Repository表示的是"数据访问层",也称为"持久层",负责数据访问操作
@Configuration表示的是"配置层",处理项目中的一些配置信息
@Component表示的是"组件层"
这五个注解表示的含义是不一样的,但是用法是差不多的

上图是程序的应用分层
(5).使用方法注解获取对象
Ⅰ.介绍
@Bean,为"方法注解"。方法注解的主要用途为:①.一个类,创建了多个对象②.使用外部包里的类,没有办法添加类注解
对于①来说当一个类创建了多个对象的时候,如果我们通过类来访问对象,由于有多个对象,所以Spring就不知道该使用哪个对象了
对于②来说,没有办法添加类注解的原因是因为,要想添加类注解,前提是能修改这个类的源代码 。对于外部包里的类来说,是第三方jar包里的类,我们是拿不到源码的,所以不能打开这个类文件,所以就不能在类上加类注解 。但是@Bean就不一样了,@Bean注解不写在外部目标类上,而是写在我们自己创建的配置类的普通方法上 。这个方法是由我们自己手动编写的,那么我们就可以new一个外部类的对象,然后在方法上加@Bean注解,那么Spring就会执行这个方法,把方法返回的对象自动存入IoC容器,作为Bean管理 。整个全程,我们不需要改动外部类的一行代码,只需要在自己项目的配置类里声明即可
注意:在使用方法注解的时候,要搭配着类注解一块使用
可以看到,报错了,原因没有找到一个"StudentController"类型的对象。
这是因为方法注解要和类注解一起使用。因为类注解会让Spring扫描这个类,如果不加类注解,则Spring会直接忽略这个类。@Bean注解本身没有"让类被扫描"的能力,它只负责当类被加载后,执行该方法,将返回值放入IoC容器
细节:@Configuraiton这个注解是专门用来批量定义@Bean的注解,会做CGLIB增强,解决多@Bean之间依赖重复创建对象的问题
当加上@Controller注解之后,就不报错了
Ⅱ.测试

可以看到,当一个类有多个对象的时候,Spring期望只有一个匹配,结果发现了两个 ,此时就报错了。同时我们发现,@Bean注解的名称就是他们的方法名。
下面,我们通过方法名来进行获取对象

可以发现,获取成功了
Ⅲ.重命名
我们还可以对类进行重命名

当我们看源码的时候,我们发现,value()的类型是一个String数组,所以我们进行命名的时候可以通过数组的形式来进行命名

Ⅳ.方法上加参数

当携带了一个参数的时候,Spring会从Spring容器中,查找该参数类型的对象,然后进行赋值。例如,当携带了一个String类型的参数的时候,Spring会从Spring容器中,查找String类型的对象,并且赋值给name
3.扫描路径
扫描路径,指的是启动类的扫描路径。Spring默认扫描路径为"启动类"所在的路径,包括子孙目录。

可以看到,当我将启动类移动到ripository包中的时候,再运行,发现报错了。这是因为当我将启动类移动到repository包中后,启动类扫描的路径就为repository这个包了,并不会扫描其他包了。要想解决该问题,可以使用 @ComponentScan注解来配置扫描路径


可以看到,事实上,@ComponentScan注解已经包含在了启动类声明注解@SpringBootApplication中了,默认扫描的路径是S平日那个Boot启动类所在包及其子包

