控制反转和依赖注入

Posted by Kanon on December 5, 2017

写这篇文章的初衷,是在面试时老被问到控制反转和依赖注入是什么意思,而自己的回答无外乎两句话:减少模块间的耦合;将对象间的依赖交给Spring来管理。

这样的回答说好听点是简单明了,说难听点是一笔带过,差强人意。如果面试官再往深层次问,自己认知的浅薄怕是毫无招架之力。

本着一个坑绝不能摔倒三次的原则,在对控制反转和依赖注入做了进一步研究之后,写下此文欲填此坑!

一个螺丝刀刀柄和刀头的例子

用螺丝刀刀柄和刀头的例子,就能很清晰地解释什么是控制反转和依赖注入。

假设这样一种情况,刀柄和刀头是分离的两种东西,要装配成一根螺丝刀需要两者兼备。刀柄和刀头之间存在着依赖关系,刀柄依赖刀头,只有当刀头创建好了,才能创建刀柄。

我们就可以根据这样的情况设计两个类,一个刀柄,一个刀头,刀柄的构造函数里需要传入一个参数,也就是刀头的对象。

在正常情况下,我们需要先new一个刀头,再将这个刀头传入刀柄的构造函数,由此构造出刀柄。那么这就是一根可以使用的螺丝刀了。

注意这里!!!是由我们来直接控制去获取依赖对象(刀头)。那么在有了Spring之后呢?依赖对象(刀头)的创建无需我们来操心,而是交给一个叫做IOC容器的东西来进行管理,至于IOC容器是如何创建那个对象,以及什么时候创建好对象的,刀柄不需要关心这些细节问题。

OK到这里已经很清晰了,我们进一步解释下这两个名词

控制反转(Ioc—Inversion of Control):把查找和创建依赖对象的控制权交给IOC容器(原来的控制权是在我们手中)
依赖注入(DI-Dependency Injection):由容器帮我们创建以及注入所需的依赖对象

控制反转和依赖注入的关系

控制反转是一种设计的思想,而依赖注入是控制反转的具体实现

优点

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

Talk is cheap, show me the code!

具体代码实现

pom.xml里添加Maven依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.netease.course</groupId>
  <artifactId>spring-container</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <dependencies>
  	<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>4.2.1.RELEASE</version>
  	</dependency>
  </dependencies>
</project>

配置文件application-context.xml

<?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"
	xmlns:p="http://www.springframework.org/schema/p"
	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">
	
	<context:component-scan base-package="com.netease.course"/>
	
	<bean id="screwdriver" class="com.netease.course.ScrewDriver" scope="prototype">
		<constructor-arg>
			<ref bean="straightheader"/>
		</constructor-arg>
	</bean>
	
	<bean id="straightheader" class="com.netease.course.StraightHeader">
		<constructor-arg name="color" value="red"></constructor-arg>
		<constructor-arg name="size" value="10"></constructor-arg>
	</bean>
</beans>

然后在同一个包下面依次创建这三个类

1.Header.java

package com.netease.course;

public interface Header {
	public void doWork();
	public String getInfo();
}

2.StraightHeader.java

package com.netease.course;

//StraightHeader实现Header的接口
public class StraightHeader implements Header {
	private String color;
	private int size;
	
	public StraightHeader(String color, int size) {
		this.color = color;
		this.size = size;
	}
	
	public void doWork() {
		System.out.println("Do work with straight header");
	}

	public String getInfo() {
		return "Info:color="+color+",size="+size;
	}

}

3.ScrewDriver.java

package com.netease.course;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;



public class ScrewDriver {
	private Header header;
	private String driverColor;
	
//	构造函数接受Header的实现类
	public ScrewDriver(Header header) {
		this.header = header;
	}
	
	public void setDriverColor(String color) {
		this.driverColor = color;
	}
	
	public void showInfo() {
		System.out.println("Use " + driverColor + " screwdriver. " + "Use header:" + header.getInfo());
	}
	
	@PostConstruct
	public void init() {
		System.out.println("init the screwdriver");
	}
	
	@PreDestroy
	public void destroy() {
		System.out.println("destroy the screwdriver");
	}
	
}

创建测试的main函数

package com.netease.course;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

	public static void main(String[] args) {
                //ApplicationContext可以理解为IOC容器
		ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
		ScrewDriver obj1 = context.getBean("screwdriver", ScrewDriver.class);
		obj1.setDriverColor("green");
		obj1.showInfo();
		ScrewDriver obj2 = context.getBean("screwdriver", ScrewDriver.class);
		obj2.setDriverColor("red");
		obj2.showInfo();
		System.out.println();
		
		//依赖注入
		StraightHeader obj3 = context.getBean("straightheader", StraightHeader.class);
		obj3.doWork();
		System.out.println(obj3.getInfo());
		System.out.println();
		
		//验证StraightHeader这个bean是单例
		StraightHeader obj4 = context.getBean("straightheader", StraightHeader.class);
		System.out.println(obj3);
		System.out.println(obj4);
		System.out.println();
		
		((ConfigurableApplicationContext)context).close();
	}

}

程序运行结果

init the screwdriver
Use green screwdriver. Use header:Info:color=red,size=10
init the screwdriver
Use red screwdriver. Use header:Info:color=red,size=10

Do work with straight header
Info:color=red,size=10

com.netease.course.StraightHeader@5c18298f
com.netease.course.StraightHeader@5c18298f

延伸知识点

在上面的main函数里,我也同时测试了下单例和多例两种模式,bean默认情况下是单例的。单例模式的对象在整个系统中只能有一个,多例模式可以有多个实例。