IoC控制反转介绍

218 阅读8分钟

IoC 概述

[内容时间:2022.01]

控制反转 IoC( Inversion of Control )是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器。通过容器来实现对象的装配和管理(赋值、创建等操作)。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理

IoC 是一个概念,是一种思想,其实现的方式多种多样。当前比较流行的实现方式是依赖注入,应用较广

  • 例子

    正转:手动在程序代码里 new 一个对象,就是正转

    反转:之前使用过的 JavaWeb 技术中,servlet 由Tomcat 自动创建,这就是控制反转的思想

依赖注入 DI (Dependency injection)

依赖注入是指,程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序

Spring 的依赖注入对调用者与被调用者集合没有任何要求,完全支持对象之间的依赖关系的管理

  • Spring 框架使用依赖注入( DI )实现 IoC

    Spring 容器是一个超级大工厂,负责创建、管理所有 Java 对象,这些呗管理的 Java 对象被称为 Bean 。这些 Bean 之间的依赖关系由 Spring来管理。而 Spring 采用 “依赖注入” 的方式来管理 Bean之间的依赖关系,使用 IoC 实现对象之间的解耦合

基于 XML 的 DI

注入分类

注入,大白话就是 初始化

bean 在使用无参构造器创建对象后,要对里面的每个属性进行初始化赋值。初始化就是由容器自动完成的,称为注入。

根据注入(初始化)方式的不同,常用的有两类:① set 注入,② 构造注入

set 注入

set 注入,也叫设值注入。通过 set 方法将初始化的值传入被调用者的实例的属性中。这种注入方式简单、直观。因此在 Spring 的依赖注入中大量使用

注入简单类型

要求:若要在 XML 文件中初始化赋值,对象与 XML 文件中声明的对应属性,必须有 set 方法,否则抛异常

<!-- 在 applicationContext.xml 文件中声明 bean 的格式 -->
<!--
    bean 的 id:代表此对象的自定义名称,可以用于以后的 “ref” 属性中,同时是在 Java 程序中获取对象的值
    bean 的 class:类的全限定名
    property 的 name:表示User中的属性名,一定是要求User里有的属性
    property 的 value:表示要赋的值
--><bean id="myUser" class="com.gg.entity.User">
    <property name="name" value="张三"/><!-- setName("张三") -->
    <property name="age" value="18"/><!-- setAge("18") -->
</bean>
//在 java 程序中获取容器中的 beanpublic void test(){
    String config = "applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    User user = (User) ac.getBean("myUser");
}

注入引用类型

<bean id="myUser" class="com.gg.entity.User">
    <property name="name" value="张三"/>
    <property name="age" value="18"/>
</bean><bean id="myPeople" class="com.gg.entity.People">
    <!-- 对于其它 Bean 对象的引用,使用<bean/>标签的 ref 属性 -->
    <property name="user" ref="myUser"/>
</bean>

构造注入

构造注入是指,在构造调用者实例的同时,完成被调用者的实例化(引用类型属性的赋值)

即使用构造器设置依赖关系要求 :必须有一个有参的构造方法

注意:若不使用 index 属性,参数顺序必须与构造方法的形参顺序一致使用方式

<bean id="myUser" class="com.gg.entity.User">       
    <constructor-arg name="name" value="张三" />      
    <constructor-arg name="age" value="20" />       
    <constructor-arg name="address" ref="myAddress" />
</bean>

引用类型的自动注入

对于引用类型属性的注入,也可不在配置文件中声明(赋值),为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)

根据自动注入判断标准的不同,可以分为两种:

byName:根据名称自动注入

byType:根据类型自动注入

byName 方式自动注入

当配置文件中的被调用者的 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用 byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。

注意:要在 bean 标签中设置 autowire="byName" 属性才可使用byName自动注入

Java bean 中 User 的属性:

public class User{
    private String name;
    private String age;
    private String address;
}

在配置文件中:

<!-- 声明Address对象 -->
<!-- id值要与User里的address属性名一样 -->
<bean id="address" class="com.gg.entity.Address">       
    <property name="city" value="中国" />     
    <property name="phone" value="1234567890" />
</bean><!-- 声明User对象 -->
<bean id="myUser" class="com.gg.entity.User" autowire="byName">     
    <property name="name" value="张三" />     
    <property name="age" value="20" />
    <!-- 
        此处的address属性自动赋值        
        隐含了:<property name="address"  ref="address" /> 
    -->
</bean>

byType 方式自动注入

要求:配置文件中被调用者 bean 的 class 属性指定的类,要求与代码中调用者 bean 类的某引用类型属性类型同源。

同源的意思是:类型相同,或子类,或实现类。

注意:但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知道该匹配哪一个了。就会抛异常

为应用指定多个 Spring 配置文件

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加。导致配置文件变得非常庞大臃肿。为了避免这种情况的产生,提高配置文件的可读性以及可维护性,可以将 Spring 配置文件分解成多个配置文件。

包含关系的配置文件

多个配置文件中,有一个总文件。总配置文件将各文件(或子文件)通过 < import / > 标签引入。在 java 代码中只需要使用总配置文件对容器进行初始化即可。

<import resource="classpath:spring-service.xml" />
<import resource="classpath:spring-dao.xml" />

也可以使用通配符来包含多个文件。但总配置文件不能符合通配符的包含文件,避免递归无限循环。

<import resource="classpath:spring-*.xml" />

基于注解的 DI

使用注解的方式,就不用在 Spring 配置文件里面声明 bean 对象了

  • 注意:Spring 中使用注解,需要在原有的 Spring 运行环境基础上再做一些改变

    需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解

指定多个包的三种方式

1)使用多个 context:component-scan 指定不同的包路径

<context:component-scan base-package="com.gg.entity" />

2)base-package 的值中使用分隔符

<!-- 使用逗号分隔(半角) -->
<context:component-scan base-package="com.gg.entity,com.gg.service" /><!-- 使用分号分隔(半角) -->
<context:component-scan base-package="com.gg.bean;com.gg.controller">

3)base-package 指定到父包名

<!-- 指定到父包名 -->
<context:component-scan vase-package="com.gg" /><!-- 顶级包名 -->
<!-- 但不建议使用顶级父包,扫描路径较多,导致容器启动时间满。效率低下! -->
<context:compunent-scan base-package="com" />

定义 Bean 的注解 @Component (掌握)

需要在类的上方使用注解 @Component,该注解的 value 属性用于指定该 bean 的 id 值

// 注解中的参数省略了 value 属性,该属性用来指定 Bean 的 id。@Component(value="myStudent")
// 若不指定value属性,bean 的 id 默认为类名的首字母小写:student、helloWorld
@Component("myStudent")
public class Student{
    private String name;
    private int age;
}
  • 提示:Spring 还提供了另外三个创建对象的注解

    @Repository 对 DAO 实现类进行注解

    @Service 对 Service 实现类进行注解

    @Controller 对 Controller 实现类进行注解

    这三个注解与 @Component 都可以创建对象。但这三个注解都还有其它的含义,@Service 创建业务层对象,@Concroller 创建的对象作为处理器接收响应用户的请求

    @Repository,@Service,@Controller 是 @Component 的细化。加强版。标注不同层的对象:持久层,业务层,控制层

简单类型属性注入 @Value (掌握)

  • 需要在属性上使用注解 @Value,这个注解的 value 属性就是需要赋值的属性值
  • 使用 @Value 完成属性注入不需要 setter 方法。如果有,也可以加在 setter 方法上

语法格式:

@Ccomponent
public class Student{
    @Value("张三")
    private String name;
    @Value("18")
    private int age;
}

byType 引用类型自动注入 @Autowired (掌握)

需要在引用数据类型的属性上加上 @Autowired ,默认是 byType

直接使用 @Autowire 是很方便的。但是要注意,Spring 容器池里只能有一个符合 byType 匹配机制的 Bean 对象。超过一个,就会报错(不知道匹配哪一个,发生了冲突)

语法格式:

@Component("mySchool")
public class School{
    @Value("清华大学")
    private String schoolName;
}
​
@Component("myStudent")
public class Student{
    @Value("zhangsan")
    private String name;
    @Value("20")
    private int age;
    @Autowired  //默认byType自动装配Bean
    private School school;
}

byName 引用类型自动注入 @Autowired 与 @Qualifier

因为 @Autowire 默认使用的是 byType,所以有时候需要手动设置为 byName 比较泛用

@Qualifier 的 value 属性,用来匹配 Bean 的 id 值。同样对 setter 方法无需求

语法格式:

@Component("mySchool")
public class School{
    @Value("清华大学")
    private String schoolName;
}
​
@Component("myStudent")
public class Student{
    @Value("zhangsan")
    private String name;
    @Value("20")
    private int age;
    
    // 使用 byName 注入
    // value同样可以省略,写成:@Qualifier("mySchool")
    @Qualifier(value="mySchool") 
    @Autowired  
    private School school;
}

@Autowire 中的 require 属性

  • 默认为 true。代表注入失败后,程序会抛出异常终止运行。

  • 若改成 false ,程序会继续运行。但是类中的属性就无法赋值成功,初始化失败,属性值是 null。

    为了方便查找 bug ,建议使用默认的:require=true

@Autowire(require=true)
private School school;

JDK 注解 @Resource 自动注入 (掌握)

Spring 提供了对 JDK 中 @Resource 注解的支持。@Resource 注解既可以按名称匹配 Bean ,也可以按类型匹配 Bean。默认是 byName 自动注入。使用这个注解,要求 JDK 必须是 6 以上的版本。

注意:JDK11 以上放弃了对 @Resource 注解的支持。需要在 Maven 中重新导入 @Resource 注解的依赖。

@Resource 注解,既可以在类中的属性上,也可以在 setter 上。对 setter 无要求(和自动注入 @Autowire 一样)

byType 方式

@Resource 注解若不带任何参数,默认 byName,若 byName 不能满足注入,则会自动按照 byType 进行 Bean 的匹配注入。

byType.png

byName方式

@Resource 可以指定 name 属性,用于固定按照 byName 来对 Bean 的 id 匹配

5EUSY()OS)@QU_VXFDS}IAG.png

@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。

C(3J}FNT}KEXSK)$55LJ2.png

注解与 XML 的对比

注解的优点

  • 方便
  • 直观
  • 高效(代码量少,没有 XML 文件那么复杂)

注解的弊端

以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的,耦合度比 XML 文件方式要高

XML 方式的优点

  • 配置和代码是分离的
  • 在 XML 中做修改,无需重新编译代码,只需要重启服务器就能将新的配置加载

XML 的缺点

编写麻烦,效率低,大型项目过于复杂