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 程序中获取容器中的 bean
public 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 的匹配注入。
byName方式
@Resource 可以指定 name 属性,用于固定按照 byName 来对 Bean 的 id 匹配
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
注解与 XML 的对比
注解的优点
- 方便
- 直观
- 高效(代码量少,没有 XML 文件那么复杂)
注解的弊端
以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的,耦合度比 XML 文件方式要高
XML 方式的优点
- 配置和代码是分离的
- 在 XML 中做修改,无需重新编译代码,只需要重启服务器就能将新的配置加载
XML 的缺点
编写麻烦,效率低,大型项目过于复杂