控制反转(IOC)是将由程序控制的”对象间的依赖关系”转交给IoC容器来进行控制
首先,控制反转是为了降低调用者和被调用者之间的依赖关系,在通常的程序设计过程中,调用者通常会直接使用new关键字创建被调用者的实例,这样就造成了调用者和被调用者之间的耦合度很高,不利于软件的移植和维护。
在这里,通过控制反转就能解决前面的问题,控制反转通过将与被调用者有关的操作定义在一个配置文件当中,只需要适时修改配置文件即可解决调用者与被调用者之间的依赖关系。
实质上,控制反转是将对象的实例化交给了IoC容器,而非由调用者去创建。IoC容器会根据配置文件中配置的信息进行创建,对象的属性也是有IoC容器注入到实例对象中的。
这里就牵扯到了IoC容器对对象属性的配置,这里引入了一个词,依赖注入,其实实质上就是对由IoC创建的对象属性进行填充。
依赖注入包含三种方式:
- 接口注入
- 属性注入(Setter方法注入)
- 构造方法注入
首先是接口注入,如下一段代码:
public class ClassA {
private InterfaceB bInterface;
public void doSomething() {
Ojbect obj = Class.forName(Config.ClassBImp).newInstance();
bInterface = (InterfaceB)obj;
bInterface.doSomething();
}
...
}
通过上面代码可以看到有接口B以及它的实现类ClassBImp,首先通过Class.forName传入参数创建一个ClassBImp的实例,然后将其转换成B的接口InterfaceB,这样的方式暂时没有理解的特别到位,使用较多的是后两种注入方式。
属性注入(Setter注入)
属性注入就是在接受注入的类中定义属性的setter方法,并在配置文件的参数中定义需要注入的元素,如下,有一个MessageBean:
public class MessageBean {
private String message;
private RefBean refBean;
public RefBean getRefBean() {
return refBean;
}
public void setRefBean(RefBean refBean) {
this.refBean = refBean;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
在其中有两个属性,其中一个属性是String,另一个是一个RefBean的对象,其中声明了两个属性的Setter方法,RefBean如下:
public class RefBean {
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
然后就是配置文件的部分了,如下:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="messageBean" class="com.chinasei.spring.MessageBean">
<property name="message">
<value>Spring IoC</value>
</property>
<property name="refBean">
<ref bean="refBean" />
</property>
</bean>
<bean id="refBean" class="com.chinasei.spring.RefBean">
<property name="msg">
<value>属性注入测试!!!</value>
</property>
</bean>
</beans>
上面的配置文件中定义了两个Bean,分别对应RefBean和MessageBean,然后分别对两个Bean进行了属性注入,其中使用指明了要进行注入的属性,然后通过value标签进行值的设置,其中对于MessageBean对象的refBean属性,是通过标签来进行设置的,此处的bean直接对应在配置文件中定义的Bean。
上面是Bean以及通过属性注入进行依赖注入的配置文件,下面是使用:
public class DISetterTest {
public static void main(String[] args){
ApplicationContext ac = new FileSystemXmlApplicationContext("src/beans-config.xml");
//ApplicationContext ac = new ClassPathXmlApplicationContext("beans-config.xml");
//ApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"beans-config1.xml","beans-config2.xml"});
MessageBean message = (MessageBean)ac.getBean("messageBean");
System.out.println(message.getMessage() + message.getRefBean().getMsg());
}
}
其实通过测试的源码也可以清楚的看到,这里不再使用new的方式创建对象,而是首先创建了一个ApplicationContext对象,ApplicationContext是一个应用上下文关系,通过读取配置文件就可以得到Bean之间的关系。然后通过ac对象的getBean方法来获取指定的对象,在得到指定的Bean的过程中,由于在配置文件中已经设置了属性注入,所以创建好的对象已经对属性进行了赋值操作,直接可以使用。
在上面的测试方法中,ApplicationContext的创建包括注释的部分内容是通过不同的方式加载配置文件,第三种配置文件的加载,可以传入一个字符串数组,加载多个配置文件。
构造注入
如同名字一样,这种注入方式就是通过构造方法进行注入,依旧通过上面的例子,在MessageBean中添加构造方法:
public class MessageBean {
private String message;
private RefBean refBean;
public MessageBean() {
}
public MessageBean(String message, RefBean refBean) {
this.message = message;
this.refBean = refBean;
}
public RefBean getRefBean() {
return refBean;
}
public void setRefBean(RefBean refBean) {
this.refBean = refBean;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
然后配置配置文件如下:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="messageBean" class="com.chinasei.spring.MessageBean">
<constructor-arg>
<ref local="refBean"/>
</constructor-arg>
<constructor-arg>
<value>Spring IoC</value>
</constructor-arg>
</bean>
<bean id="refBean" class="com.chinasei.spring.RefBean">
<property name="msg">
<value>属性注入测试!!!</value>
</property>
</bean>
</beans>
测试方法同属性注入。
通过上面的配置文件就可以发现,在bean标签中包含了子标签constructor-arg,这个标签就是用来设置构造参数的。
使用构造注入需要注意到,value元素中的数值类型的值也是用字符串类型的形式表示的,值得具体类型在进行注入的时候才能决定,同时,构造注入是按照构造参数的设置顺序为构造方法注入两个参数的值。
构造注入的constructor-arg元素提供了type属性和index属性,可以指定构造方法中的参数类型和配置顺序,是比较直观的。