【JavaEE】(14) Spring IoC 和 DI

一、什么是 Spring IoC

1、概念

Spring 是包含众多工具方法的 IoC 容器。

  • 包含众多工具方法:比如 Sring 核心包里 util 文件下的 StringUtil 类里的静态方法,等。
  • 容器 :存、取东西的器皿。在这个场景下,存、取 的东西是对象(bean)
  • IoC:Inversion of Control(控制反转),一种思想。

Spring IoC 就是指:对象的生命周期的控制权,由对象的调用者掌握,反转为由 Spring 掌握

2、传统程序开发

例如,设计汽车类:

但这种依赖,会导致:当汽车有更个性化的需求,比如加上颜色时,需要修改每个部件的参数列表:高耦合,一个模块的修改会导致其它模块一起修改。

3、IoC 程序开发

将车身、底盘、轮胎的创建外包出去,即:将每个对象的创建、销毁交给 Main 控制,不再由调用者控制:

java 复制代码
package com.edu.springiocdemo.v2;

public class Car {
    private Framework framework;

    // size: 轮胎大小
    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("汽车已创建...");
    }

    public void run() {
        System.out.println("汽车正在运行...");
    }
}


package com.edu.springiocdemo.v2;

public class Framework {
    private Bottom bottom;

    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("车身已创建");
    }
}


package com.edu.springiocdemo.v2;

public class Bottom {
    private Tire tire;

    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("底盘已创建...");
    }
}


package com.edu.springiocdemo.v2;

public class Tire {
    private int size;

    public Tire(int size) {
        this.size = size;
        System.out.println("轮胎尺寸:" + size);
    }
}

增加颜色需求,只需要修改 Tire、Main 即可:

IoC 思想:将依赖类的控制权外包给别人,使用者只管使用,无需关心依赖类的实现细节

优点:

  • IoC 容器集中管理对象,方便后续存放、使用。(Spring 中,通过注解就能简单实现)
  • 使用者无需了解创建对象的实现细节,降低了耦合度。

Spring IoC 就是 Spring 实现的 IoC。

二、什么是 Spring DI

1、概念

DI (Dependency Injection) 就是依赖注入,通俗讲,就是依赖赋值(把对象从容其中取出来使用)。而 Spring DI 就是 Spring 实现的 DI。

2、Spring IoC & DI 的使用例子

回到图书管理系统:对于 BookDao、BookService,每一次创建的对象的功能实际上是一样的,那我们只需要创建一个对象放到容器(@Component);使用时,从容其中取出对象(@AutoWired)。

三、IoC 详解

1、Bean 的存储

  • 类注解:@Controller(控制层)、@Service(业务逻辑层)、@Repository(数据访问层)、@Component(组件)、@Configuration(配置层)。
  • 方法注解:@Bean。

2、类注解(五大类注解)

五大类注解 ,在业务逻辑上的含义不同 ,分别管理不同层次的类,主要是为了分门别类提高可读性。但功能上 @Component 可以替代 @Service、@Repository、@Configuration (其余四个类注解,都是在 @Component 基础上延伸的),除了不能替代 @Controller(功能比 @Component 多一点,还负责接收用户传递的数据,所以我们必须在路由映射的类上加 @Component,表示这个接口类由 Spring 管理了。因此我们调用接口时也没有 new 对象)。

(1)Bean 的命名

  • 默认情况:类名的小驼峰写法。
  • 特殊情况(类名前两个字母是大写):保持类名不变,如 UController。

(2)按 Bean 类型获取 Bean

Spring Boot 项目的入口方法中,调用了一个 run 方法,它的返回类型与 "应用上下文" 有关:

应用上下文中包含了 Spring 管理的 Bean,我们通过它获得 Bean:

按 Bean 的类名获取:

如果没有加 @Controller,报错找不到 xxx 类型的 bean:

(3)按 Bean 名称获取 Bean

因为没有提供 Bean 类型,所以返回的 Bean 是 Object,需要强转:

如果 Bean 写错,会报错,找不到命名为 xxx 的 Bean:

(3)两者都有

先按类名找,找不到再按命名找。

可以看到,通过三种方法获取的 Bean,都指向同一个地址,因此,Spring 中的 IoC 容器应用了单例模式(对于某个类,只加了类注解):

只按类型获取 Bean 具有缺陷:**当一个类型有多个 Bean 时,无法获取。**在方法注解中详解。

3、方法注解(@Bean)

适用场景:

  • 无法修改第三方类,加不了类注解。
  • 需要为同一个类型创建多个 Bean。

命名规则:方法名

使用 @Bean 创建两个 Bean:

如果依然使用按类型获取 Bean,则会因为同一类型的 Bean 有多个,而不知道返回哪个 Bean,产生报错:

某个类存在多个 Bean 时,需按命名获取

4、修改 Bean 的默认命名

类注解:只能重命名一个命名

方法注解:一个 Bean 可以有多个命名

5、扫描路径(@ComponentScan)

由于整个项目中的代码很多(包括第三方库),所以如果启动类通过扫描所有路径下的文件,来知道哪些地方使用了类注解、哪些地方使用了方法注解,就会非常慢。因此,Spring 只会扫描跟 启动类同路径下的代码

也可以修改扫描路径,修改后可以扫描到 controller 包下的 UserController.java:

四、DI 详解

IoC 的注解用来标识把哪些 Bean 交给 Spring 管理。DI 的注解就用来取出 Bean 使用。

1、属性注入

在属性上加 @AutoWired:

java 复制代码
package com.edu.springiocdemo.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void hello() {
        System.out.println("Hello from UserService");
    }
}



package com.edu.springiocdemo.component;

import org.springframework.stereotype.Component;

@Component
public class UserComponenet {
    public void hello() {
        System.out.println("Hello from UserComponent");
    }
}

运行:

2、构造方法注入

将要注入的 Bean 作为形参,并在构造方法上加 @AutoWired。

  • 只有一个 构造方法时,@AutoWired 可省略,类创建对象时执行该唯一构造方法。
  • 当有多个 构造方法,且有无参构造方法时,@AutoWired 可省略优先执行无参构造方法
  • 当有多个 构造方法,且没有无参构造方法 时,需通过 @AutoWired 指定优先执行哪个构造方法。

情况1:

情况2:如果不让无参构造优先,可在另一个有参构造方法上加 @AutoWired。

优先执行无参,bean 未被注入:

情况3:

修改:

3、Setter 注入

跟属性注入类似,set 方法上加 @AutoWierd:

4、三种注入的优缺点

(1)属性注入

优点:

  • 使用简单,程序员最爱。

缺点:

  • 不能注入被 final 修饰的属性。
  • 只能用于 IoC 容器。

(2)构造方法注入(Spring 4.X 推荐,目前推荐)

优点:

  • 可以注入被 final 修饰的属性。
  • 注入对象不被修改(final 修饰)。
  • 注入对象保证被完全初始化,因为构造函数是在类加载阶段就被执行的方法。
  • 通用性好,由 JDK 提供,因此所有框架都能使用。

缺点:

  • 注入多个对象时,构造函数代码比较复杂。

(3)Setter 注入(Spring 3.X 推荐,以前推荐)

优点:

  • 方便在类实例后,重新对 bean 进行修改(再 set)。

缺点:

  • 因为能反复调用 set,又有被修改的风险。

5、@AutoWired 存在的问题

(1)存在的问题

@AutoWired 默认按类注入(如果属性名是 bean 的命名,按照命名注入。如果 @Qualifier 指定 bean 命名,优先按照指定的命名注入)。

@AutoWired 默认按类注入,不知道注入哪个 bean。

UserComponent.java:

报错说可以使用这俩:

(2)解决办法1(@Qualifier)

如果属性名是 bean 的命名,按照命名注入:

如果 @Qualifier 指定 bean 命名,优先按照指定的命名注入:

(2)解决办法2(@Primary)

在优先的方法注解上加:

(3)解决办法3(@Resource,JDK 提供)

用 @Resource 指定命名:

(4)@AutoWired 和 @ReSource 的区别

  • @AutoWired 是由 Spring 框架 提供的注解,@Resource 是由 JDK 提供的注解(可适用于任何框架)。
  • @AutoWired 默认按类注入,@ReSource 默认按命名注入。

五、练习

将图书管理系统中的 Service 层、Dao 层的注解改成 @Servece、@Repository:

六、Spring、Spring Boot、Spring MVC 的区别和联系

它们都是开发应用框架。

(1)Spring:主要功能:管理对象、对象之间的依赖(IoC&DI);⾯向切⾯编程(AOP);数据库事务管理;web 框架支持(Spring MVC)等。突出优点:高度开放性,可自由选择 Spring 的部分或全部功能,也可接入第三方框架,如 MyBatis。

(2)Spring MVC:是一个 web 框架,是 Spring 的一个子框架。主要功能:开发 web 应用和网络接口(服务器与用户交互的部分,如 @RequestMapping)。

(3)Spring Boot:对 Spring 的一个封装,让开发更快速,让开发者专注于开发,而无需过多关注 XML 的依赖配置(以前都是通过 xml 自己写依赖关系,现在用注解就行。并且以前程序员必备技能就是排包,解决包的版本冲突,现在 Spring Boot 帮我们整理好了,从中央仓库找到坐标复制到 POM 文件就行)和底层实现。