一、什么是IOC
学习过Spring的我们都知道,Spring 是一个分层的 Java SE/EE 应用、并且以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核的轻量级开源框架。
什么是IOC
IOC,也叫做控制反转,其大体思路就是通过第三方的容器实现各个组件之间的松耦合,在日常程序开发过程当中,我们推荐面向抽象编程,但是在面向抽象编程会产生大量的类的依赖,对于大量对象之间的依赖关系如果交由对象自己管理,对于后期的维护成本是十分高昂的。基于这种设计理念, Sring为我们提供一个管理这些依赖对象的容器,将类的创建反转给容器,使得我们不需要再去关注对象的产生以及它们之间的依赖关系。即在程序设计中各个组件中存在的依赖关系将有这个容器在运行中动态决定,容器能将这些组件依赖的目标对象注入到相关组件当中,这项关键技术我们称之为控制反转(IOC)。
实现方式——依赖注入
在IOC的实现方式中存在两种实现方式——依赖注入和依赖查找,但是由于需要使用到容器的API去查找自己所依赖的目标对象,造成在容器外无法去使用对象,所以依赖查找的方式已经被抛弃。
依赖注入(DI)是Spring所使用的方式,在Spring官方解释中,控制反转(IOC)也叫做依赖注入(DI),对于控制反转就是所依赖的对象反转交由第三方来创建,完成创建的对象被注入到目标组件当中,所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。
二、解耦合——依赖注入
程序的耦合性和内聚性
耦合性,是对模块间关联程度的度量。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间联系越多,其耦合性越强,同时表明其独立性越差。如果对象之间的耦合越高,维护成本越高。内聚是一个模块内各个元素彼此结合的紧密程度,它描述的是模块内的功能联系。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事,高内聚的模块也就意味着对象依赖程度降低,耦合度降低。因此在程序的设计当中,划分模块的一个准则就是高内聚低耦合。
在任何一个实际应用开发当中,该应用都是有多个对象相互协调完成,一个类需要管理它所依赖的所有对象,由对象本身管理依赖的对象必将导致程序的高耦合和测试的难度。
一个简单示例:
interface Fruits {
void eat();
}
class Apple implements Fruits{
@Override
public void eat() {
System.out.println("吃苹果");
}
}
public class People{
private Fruits fruits;
public People(){
fruits = new Apple(); // apple和People耦合
}
public void eatFruits(){
fruits.eat();
}
}
可以看出,Main对象中依赖一个水果类(fruits),而对象main通过new的方式去管理所依赖的对象,使得Apple类和它紧密耦合到了一起,当我们需要得到一个苹果,该对象可以实现,但如果我们需要换到另一种水果,该对象就无法实现了。
解耦合
但是在实际开发中,一定程度的耦合是必然的,完全解耦的程序时没有实际意义的。
通过IOC,对象中他们的依赖关系将创建方式将有一个第三方的组件来协调完成。对象将不再需要去创建和管理它们所依赖的对象了。
public class People {
private Fruits fruits;
public People(Fruits fruits) {
this.fruits = fruits;
}
public void eatFruits(){
fruits.eat();
}
}
对比之前的实现方式,Fruits的具体实现类并没有有people类创建,它只是传入了一个fruits的基类,在此基础上,任何实现了fruit的水果类都将能够得到people的实现。如果一个对象通过接口的方式注入依赖,那么这种依赖关系就能无需改动的情况下替换不同的实现类,这就是ioc的最强大的功能——解耦合。
由于我们使用第三方来创建对象,必定需要找个结构存储,所以在应用加载时,会创建一个map集合存放对象,这个map称之为容器,在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。这个读取配置文件,创建和获取三层对象的类就是工厂。工厂就是负责给我们从容器中获取指定对象的类。其特点就是由原先的对象主动使用new的方式转换为被动由工厂创建并注入的方式。
三、依赖注入实现方式
目前依赖注入主要存在两种实现方式——基于构造方法、基于setter方法,在spring3之前还存在一种借口注入的方式,但由于不人性化的使用,在后续被废除。
引入Spring5核心依赖,
1、基于构造函方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="fruits" class="hu.Apple"></bean> //修改实现类可替换不同people类依赖对象
<bean id="people" class="hu.People">
<constructor-arg name="fruits" ref="fruits"/>
</bean>
</beans>
public class People {
private Fruits fruits;
public People(Fruits fruits) {
this.fruits = fruits;
}
public void eatFruits(){
fruits.eat();
}
}
@Test
public void test(){
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("spring.xml");
People people =(People) context.getBean("people");
people.eatFruits();
}
//输出:吃苹果
2、基于setter方法
public class People {
private Fruits fruits;
public void setFruits(Fruits fruits) {
this.fruits = fruits;
}
public void eatFruits(){
fruits.eat();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="fruits" class="hu.Orange"></bean>
<bean id="people" class="hu.People">
<property name="fruits" ref="fruits"/>
</bean>
</beans>
//输出 吃橘子
在setter方式中还提供一种名称空间注入方式,通过头文件加入注解
两种方式对比
构造方法注入的优点是在对象构造完成就可以马上使用,但是在对象依赖多时,构造方法会很长,而且因为构造方法无法被继承,没有默认值设置,导致在对一些非必须的依赖对象的处理中,需要多个构造方法。参数数量变化可能造成维护不便。
setter方法注入的灵活性更为高,虽然没法构造完成后立即使用,但是它可以被继承,更容易理解使用,所以目前被推荐使用的注入方式。