写这篇文章的初衷,是在面试时老被问到控制反转和依赖注入是什么意思,而自己的回答无外乎两句话:减少模块间的耦合;将对象间的依赖交给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默认情况下是单例的。单例模式的对象在整个系统中只能有一个,多例模式可以有多个实例。