JavaEE进阶:SpringIoC&DI

1.IoC&DI入门

在前面,我们知道了Spring是一个开源框架,让我们开发变得更简单,它支持广泛的应用场景.

更具体来说,Spring是包含了众多工具方法的IoC容器.

什么是容器?

简单来说,在编程中,List/Map这种数据结构就是数据存储容器,Tomcat就是web容器.

1.1 什么是IoC

IoC是Spring的核心思想,其实我们在之前的学习中已经用到了IoC了.

在使用类上使用@RestController,@Controller注解的时候,就是把这个对象交给了Spring进行管理.Spring框架在启动的时候就会加载这个类.

把对象交给Spring管理,就是IoC思想.

IoC: Inversion of Control.字面意思翻译:控制反转.

也就是说Spring是一个"控制反转"的容器.

1.2 IoC介绍

现在有一个需求,需要创建一个车.

车类:

java 复制代码
public class Car {
    private Framework framework;

    public Car(){
        this.framework = new Framework();
        System.out.println("cat init...");
    }

    public static void main(String[] args) {
        Car car = new Car();
        car.run();
    }
    public void run(){
        System.out.println("car run...");
    }
}

车身类:

java 复制代码
public class Framework {
    private Bottom bottom;
    public Framework() {
        bottom = new Bottom();
        System.out.println("Framework init...");
    }

}

车底座类:

java 复制代码
public class Bottom {
    private Tire tire;

    public Bottom() {
        this.tire = new Tire();
        System.out.println("Bottom init...");
    }
}

轮胎类:

java 复制代码
public class Tire {
    private int size = 17;
    public Tire(){
        System.out.println("Tire:"+ size);
    }

}

运行程序:

这样的设计看起来好像没什么问题,但是可维护性缺很低.

如果需求变更,比如需要不同轮胎大小的车,此时我们就需要指定size属性的值,而不是在创建Tire的时候就固定size的值.

但是此时会报错,我们需要将其他所有的类全部都指定size,才能把参数传过来.

改完所有的代码之后,才能正常运行,

这时候,我们就发现,以上程序的问题:当底层代码被改动之后,整个调用链往上的所有代码都需要改动,可以发现这个程序的耦合程度非常高(修改一处代码,影响其他代码的修改)

那么如何解决这个问题呢?

这个问题出现的原因,就是在创建类的时候,会同时创建下级类,比如我在创建Car的时候,就需要创建Framework类,创建Framework类,又需要创建Bottom类...

这样整个创建过程全部都连一块了,牵一发而动全身.

我们只需要不在当前类中创建下级类,就可以完成程序的解耦.

车类:

java 复制代码
public class Car {
    private Framework framework;

    public Car(Framework  framework){
        System.out.println("cat init...");
    }

    public static void main(String[] args) {
        Tire tire = new Tire(20);
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
    public void run(){
        System.out.println("car run...");
    }
}

车身类:

java 复制代码
public class Framework {
    private Bottom bottom;
    public Framework(Bottom bottom) {
        System.out.println("Framework init...");
    }

}

底座类:

java 复制代码
public class Bottom {
    private Tire tire;

    public Bottom(Tire tire) {
        System.out.println("Bottom init...");
    }
}

轮胎类:

java 复制代码
public class Tire {
    private int size;
    public Tire(int size){
        System.out.println("Tire:"+ size);
    }

}

上面这套代码,当我们需要改动尺寸的时候,只需要改动这个代码

剩下的就不需要改了,这是因为类的创建被我们放在了main函数中,无论底层怎么变化,调用链是不需要改变的,这样就完成了代码的解耦.

这种思想就是IoC思想.将程序中的对象的创建权和生命周期的管理权交给框架(这里是Spring)管理.

控制反转,也就可以理解为控制权从程序员手里反转到了Spring框架手里.

1.3 IoC的优势:

在传统的代码中,创建对象的顺序是:Car -> Framework -> Bottom -> Tire

解耦之后的代码创建顺序是: TIre -> Bottom -> Framework -> Car

在之前的代码中,Car控制并创建了Framework,Framework控制并创建了Bottom,以此类推......

这种代码,Car,Framework,Bottom,Tire 的生命周期是绑死在一起的,也就是说当Car对象没用的时候,被GC回收之后,依赖Car创建的Framework,Bottom,Tire会一起被回收.

同时这套代码无法复用,比如我只想换个Bottom,但是在new的时候,全部绑死在一起了,无法单独更换

当我们使用IoC进行更改代码之后,控制权发生反转了,所有的对象统一由Spring进行控制管理,这个时候,我们想换某一个部件就变得非常简单了.

更改之后的代码,不再是使用方创建并控制对象了,而是把依赖对象注入到当前对象中,依赖对象的控制权并不是由当前对象控制了.

这样即使依赖对象发生改变,当前的类也不会有什么变化,这就是典型的控制反转,也就是IoC的实现思想.

那么我们知道了什么是IoC思想,那么什么是IoC容器呢?

简单来说,IoC容器就是用来管理和创建对象的一个容器 .在当前代码,我们把创建和管理对象的权限交给了Spring框架,所以这里Spring框架也就是一种IoC容器.

IoC容器做的就是这下面的工作.

IoC容器具备以下优点:

资源不由使用资源的双方管理,而由不使用资源的第三方管理 ,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理 。第二,降低了使用资源双方的依赖程度.

1.资源集中管理: IoC 容器会帮我们管理一些资源 (对象等), 我们需要使用时,只需要从 IoC 容器中去取就可以了

2.我们在创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,也就是耦合度.

1.3 DI介绍

学习了IoC,让我们了解一下什么是DI

DI:Dependency Injection,中文翻译,依赖注入.

容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称为依赖注入.

在上面的代码,是通过构造函数的方式,把依赖对象注入到要使用的对象中的.

IoC属于一种思想,最终也需要一种实行方案,而DI就属于具体的实现.所以说DI是IoC的一种实现.

2.IoC&DI使用

对IoC和DI有了初步的了解,我们先学习SpringIoC和DI的具体代码实现.

既然Spring作为IoC容器,那么他就具备两个容器的基础功能:存和取.

Spring容器管理的主要是对象,这些对象,我们称为"Bean".

我们把这些对象交由Spring进行管理,由Spring负责对象的创建和销毁.我们的程序只需要告诉Spring那些需要存,以及如何从Spring中取出对象.

目标:把BookDao,BookService交给Spring管理,完成Controller层,Service层,Dao层的解耦.

步骤:1.Service层和Dao层的实现类,交给Spring管理,用@Component注解

2.在Controller层和Service层注入运行时候的依赖,用@Autowired注解

实现:

1.把BookDao和BookService交给Spring进行管理.由Spring管理对象.

2.删除创建BookDao的代码,从Spring中获取对象

更改前:

更改后:

3.删除创建BookService的代码,通过@Autowired从Spring中获取.

更改前:

更改后:

重新运行程序,发现程序功能正常.

3.IoC详解

通过上面的案例,我们已经知道了SpringIoC和DI的基本操作.接下来我们就来学习SpringIoC和DI的操作.

前面我们提到IoC控制反转.就是将对象的控制权交给SpringIoC容器.由IoC容器创建及管理对象,也就是Bean的存储.

在上面的案例中,我们要将一个对象交给IoC容器管理,需要在这个类上加上@Component注解.

而Spring框架为了更好的服务web应用程序,提供了更丰富的注解.

类注解:@Controller,@Service,@Component,@Repository,@Configuration

方法注解:@Bean

3.1 @Controller(控制器存储)

使用@Controller存储bean的代码:

我们如何知道这个对象已经在Spring中了呢?

接下来学习如何从Spring中获取对象,

运行代码:

ApplicationContext翻译过来就是Spring上下文

因为对象交给Spring管理了,所以说要想获取对象,得先获取Spring上下文(指的是Spring当前的运行环境).

如果我们把@Controller注解删掉,再次运行看看结果.发现系统给我们报错了.

这个错误信息告诉我们没有找到类型是bit.demo.ioc.UserController的bean

获取Bean的其他方式:

1.通过对象名获取,对象名就是类名把开头第一个字母改为小写,如果前两个字母都是大写就不变

比如UserController -> userController ,AManager -> AManager

2.通过类名获取

3.通过类名+对象名获取

打印这三个对象,发现是同一个对象.

3.2 @Service(服务存储)/ @Repository(仓库存储)/ @Component(组件存储)/ @Configuration(配置存储)

把之前的@Controller注解换成这四个注解,可以看到代码的效果依旧可以正常运行.

这四个注解的作用和@Controller作用是一样的,唯一的区别就是用来标识当前类的作用

这个也是和咱们前面讲的应用分层是呼应的。让程序员看到类注解之后,就能直接了解当前类的用途.

  • @Controller: 控制层,接收请求,对请求进行处理,并进行响应.
  • @Service: 业务逻辑层,处理具体的业务逻辑.
  • @Repository: 数据访问层,也称为持久层。负责数据访问操作
  • @Configuration: 配置层。处理项目中的一些配置信息.

3.3 @Bean

类注解是添加到类上的,但是存在两个问题:

1.使用外部包的类,就没办法添加类注解.

2.一个类,需要多个对象,比如多个数据源

这种时候,就需要使用@Bean注解.

方法注解需要搭配类注解使用,单纯的@Bean是没办法用的.

创建一个Student类,添加一个sutdent方法,在方法上面添加@Bean注解,但是不在类上面添加@Component注解.

尝试运行.

报错显示找不到这个类

在类上添加@component注解,再次运行.就可以拿到值了

3.4 定义多个对象

对于同一个类,如何定义多个对象呢?

比如这里有两个学生,一个叫张三,一个叫李四.

此时运行程序,发现报错说明期望只有一个结果匹配,结果发现两个.

从报错信息中我们可以知道@Bean注解的bean的名称就是他的方法名

下面我们根据名称获取对象.就可以成功拿到对象

3.5 重命名Bean

通过在@Bean注解中设置name属性就可以给Bean对象重命名,这样也可以拿到bean,这个重命名的名称可以有多个,用大括号包起来即可.

3.6 扫描路径

在前面学习的四个注解声明的bean一定会生效吗?答案是不一定,bean要生效,必须要被spring扫描.

下面我们通过更改项目工程的目录,来测试bean是否生效,

把启动路径更改位置.改到car2包里面

系统就会报错,显示没有找到这个bean

为什么会报错呢?这是因为要使用五大注解,必须要配置扫描路径,让spring扫描到这些注解.

通过@ComponentScan这个注解配置扫描路径.默认的路径是SpringBoot启动类所在的包以及子包

在启动类上添加扫描路径,就可以正确扫描到注解.

4.DI详解

刚刚我们讲解了控制反转IoC的细节,接下来我们学习依赖注入DI的细节.

在上面的案例中,我们通过@Autowired这个注解来完成依赖注入.

Spring给我们提供了三种依赖注入的方式,接下来一个一个学习.

4.1 属性注入

属性注入是使用@Autowired实现的,将Service类注入到Controller类中

运行结果,就可以实现依赖注入

4.2 构造方法注入

和属性注入的区别,构造方法注入就是将@Autowired注解加到构造方法上面,如果只有一个构造方法,@Autowired注解可以省略,如果有多个,需要用@Autowired来指明使用哪个构造方法.

4.3 Setter注入

Setter注入和属性的Setter方法实现类似,只不过在设置set方法的时候,要加上@Autowired注解.

4.4 三种注入优缺点分析

属性注入

优点:简洁,使用方便;

缺点:

1.只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)

2.不能注入一个 Final 修饰的属性

构造函数注入 (Spring 4.X 推荐)

优点:

1.可以注入 final 修饰的属性

2.注入的对象不会被修改

依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法.

通用性好,构造方法是 JDK 支持的,所以更换任何框架,他都是适用的

缺点:注入多个对象时,代码会比较繁琐

Setter 注入 (Spring 3.X 推荐)

优点 :方便在类实例之后,重新对该对象进行配置或者注入
缺点:

1.不能注入一个 Final 修饰的属性

2.注入对象可能会被改变,因为 setter 方法可能会被多次调用,就有被修改的风险.

4.5 @Autowired存在的问题

当我们在UserController中注入两个属性之后,启动程序会失败,显示发现两个bean

当有多个bean的时候,这时候可以在bean上面加上@Primary注解,来确定默认实现

我们还可以使用@Qualifier注解,指定要注入的bean的名称

或者使用@Resource指定bean的名称

常见面试题:

@Autowird 与 @Resource 的区别:

@Autowired 是 spring 框架提供的注解,而 @Resource 是 JDK 提供的注解

@Autowired 默认是按照类型注入,而 @Resource 是按照名称注入。相比于 @Autowired 来说,

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

5.练习

将我们上次的图书管理系统中的注解改为今天学习的注解

将Dao层的注解改为@Repository

将Service层的注解改为@Service

相关推荐
水煮白菜王1 小时前
Claude Code 全方位使用手册
java·开发语言·网络
Highcharts.js1 小时前
金融Web App中的复杂时序数据可视化:从选型到高性能实践
开发语言·金融·highcharts·实战代码·响应式图表
kiku18181 小时前
Docker高级管理--Dockerfile镜像制作
java·docker·eureka
郝学胜-神的一滴1 小时前
跨平台 C++ 静态库编译实战:Linux/Windows/macOS 三端统一实现
linux·开发语言·c++·windows·软件构建
xyq20241 小时前
XHR 请求详解
开发语言
ooseabiscuit1 小时前
Laravel10.x重磅发布:新特性全解析
android·java·开发语言·mysql
0xDevNull1 小时前
Tomcat 运行原理与架构深度解析
java·架构·tomcat
沐知全栈开发1 小时前
JavaScript while 循环详解
开发语言
ch.ju1 小时前
Java程序设计(第3版)第三章——数组
java·开发语言