1 什么是IoC
IoC即控制反转(Inversion of Control),设计模式的一种,不过出现的比较晚,没有被划分到GoF中。
框架的目的就是为了解耦,而IoC作为Spring的基石之一,自然也是为了解耦。那么IoC背后的思想到底是什么呢?
一般,我们对象A需要对象B的时候,我们会通过在对象A内部new一个对象B(对象是引用类型,所以可以看成对象A持有对象B的引用)
如此一来,对象A对对象B存在依赖,当修改对象B的时候,也需要去修改对象A,这样的依赖关系显然是种累赘,给对象A上了层枷锁。
此时,我们不再从对象A中直接new一个对象B了,而是把对象B交给一个容器管理,当对象A需要的时候,这个容器会new一个对象B注入到对象A,这样的话,对象A对对象B的依赖关系就发生了转变。
使用IoC,我们改变了原来依赖对象创建的方式,我们把依赖对象的创建交给了容器。
2 IoC的具体实现——依赖注入
2.1 基于构造器的依赖注入
基于构造器的依赖注入是通过容器调用有参构造器来实现的,每一个参数代表一个依赖。基于构造器的依赖注解和调用一个指定参数的静态工厂方法来创建bean的方法几乎等同。
- 无需
type
、index
和name
属性
官网给出了一个案例
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假设这三个类之间没有继承关系
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
type
属性指明构造器参数类型
type
属性可以让容器去匹配一些简单类型
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
index
属性指明构造器参数下标
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
index
指定参数下标不仅可以解决相同值所带来的歧义,还可以解决相同类型所带来的歧义。
注意 :index是从0开始的
name
属性指定参数名称
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
2.2 基于setter的依赖注入
基于setter方式的依赖注入在调用无参构造器或者无参静态工厂方法实例化bean后,容器再去调用bean中的setter方法来实现。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
选择基于构造器的依赖注入还是基于setter的依赖注入?
Constructor-based or setter-based DI?
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.
你会混淆基于构造器的依赖注入和基于setter的依赖注入,对于强制性的依赖项使用构造器,对于可选的依赖项使用setter,这是一个很好的经验法则。
The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null. Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state.
Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖关系不为空。
此外,构造器注入的组件总是以完全初始化的状态返回给客户端(调用)代码。
- 实例
- 新建一个
Address
类和Student
类
public class Address {
private String address;
/** Getter Setter */
}
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Set<String> games;
private Map<String, String> card;
private String wife;
private Properties info;
/** Getter Setter */
}
- 编写基于XML的元数据配置
beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.cap.pojo.Address" />
<bean id="student" class="com.cap.pojo.Student" >
<!-- 第一种:基本类型 -->
<property name="name" value="公孙离" />
<!-- 第二种:引用类型 -->
<property name="address" ref="address" />
<!-- 第三种:数组类型 -->
<property name="books">
<array>
<value>西游记</value>
<value>水浒传</value>
<value>红楼梦</value>
<value>三国演义</value>
</array>
</property>
<!--第四种:list -->
<property name="hobbies">
<list>
<value>听歌</value>
<value>敲代码</value>
<value>看电影</value>
</list>
</property>
<!-- 第五种:set -->
<property name="games" >
<set>
<value>LOL</value>
</set>
</property>
<!-- 第六种:map -->
<property name="card">
<map>
<entry key="身份证" value="123123123123123123" />
<entry key="银行卡" value="1234567890" />
</map>
</property>
<!-- 第7种:null -->
<property name="wife">
<null />
</property>
<!-- 第八种:properties -->
<property name="info">
<props>
<prop key="学号">20200712001</prop>
<prop key="性别">男</prop>
</props>
</property>
</bean>
</beans>
- 编写测试方法
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
}
}
- 结果输出为:
Student{
name='公孙离',
address=Address{address='null'},
books=[西游记, 水浒传, 红楼梦, 三国演义],
hobbies=[听歌, 敲代码, 看电影],
games=[LOL],
card={身份证=123123123123123123, 银行卡=1234567890},
wife='null', info={学号=20200712001, 性别=男}
}