Spring框架IOC和DI,以及XML注入
引用了博客:
https://blog.csdn.net/qq_40414738/article/details/83552219
https://blog.csdn.net/luoyepiaoxue2014/article/details/72426666 (浅谈对Spring IOC以及DI的理解)
一、IOC和DI的基本概念
Spring框架的两大特征: IOC(Inverse of Control)和DI (Dependency Injection)
我们首先要明确一点,IOC和DI不是一种技术,而是一种程序设计理念或者思路。他们俩本质是一个东西,只是一种东西的两种叫法,或者说是一个东西从两方面来描述。
控制反转和依赖注入从表面上来看,就是一种不同形式的对象创建方式。一般来说,我们如果要创建一个对象,我们是使用如下图所示的形式来创建的:
类名 变量名 = new 类名();
然而,所谓的IOC就是说,我们换一种形式来创建,例如:
类名 对象名 = (类名)context.getBean("XML配置文件中的id名");
那么为什么这玩意要叫做IOC(控制反转)还有依赖注入呢?
原本在程序中手动创建对象(new出来的一个对象)和维护对象之间的相互依赖关系的控制权,交由 Spring 框架来管理, 需要时由容器完成对象的注入即可.换句话说,IOC 反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)它解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健.比如:Class A 中用到了 Class B 的对象 b,一般情况下,需要在 A 的代码中显式的 new 一个 B 的对象。在使用了 IOC 之后呢?A 的代码只需要定义一个私有的 B 对象,不需要直接 new 来获得这个对象,而是通过相关的容器控制程序来将 B 对象在外部new 出来并注入到 A 类里的引用中。
如果上面的解释还是难以理解,那么再通俗一点讲:
Spring框架会给你提供一个容器,这个容器就是对象的外壳,再有需要的时候,我们把具体需要创建的对象的内容(也就是具体是什么对象)放到到这个容器里面,然后再推送给程序员使用。
如果实在不理解,那就再通俗一点就是:
Spring框架帮你建立了一个对象,但是具体是什么对象,这个对象里面有什么属性,都不确定,等到需要用的时候再说,等到需要用的时候,我们把具体的是什么对象告诉Spring,然后由Spring来创建这个对象,创建完成之后给到程序员使用。也就是说,整个创建对象的具体过程已经全部交给Spring来做了,我们程序员只需要告诉Spring,“来,Spring你来创建一个对象”, 就可以了,具体的创建操作,步骤,我们不需要考虑。
那么什么是依赖注入呢?
依赖注入说白点就是:在Spring生成了对象之后,我们的程序,依赖于,spring给我们提供的对象。所以这个时候Spring生成好了的对象就要“注入”到我们的程序中。这就是所谓的依赖注入。
二、XML配置文件注入
依赖注入主要的两种方式: XML配置文件注入 和 注解注入(以后会讲)
XML配置文件注入也有两种方式:setter注入 和 Constructor注入
<!--Setter注入-->
<bean id="textEditor2" class="com.example.demospringbean.TextEditor" >
<property name="spellChecker" ref="spellChecker"/>
</bean>
<!--构造函数注入-->
<bean id="textEditor1" class="com.example.demospringbean.TextEditor">
<constructor-arg ref="spellChecker"/>
</bean>
具体操作:
主程序:
package com.example.demospringbean;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp2 {
public static void main(String[] args) {
AbstractApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
System.out.println("构造器注入");
TextEditor te1 = (TextEditor) context.getBean("textEditor1");
te1.spellCheck();
System.out.println("setter注入");
TextEditor te2 = (TextEditor) context.getBean("textEditor2");
te2.spellCheck();
System.out.println("嵌套bean");
TextEditor te = (TextEditor) context.getBean("textEditor");
te.spellCheck();
System.out.println("注入集合");
JavaCollection jc=(JavaCollection)context.getBean("javaCollection");
jc.getAddressList();
jc.getAddressSet();
jc.getAddressMap();
jc.getAddressProp();
}
}
对应的XML配置文件:
```javascript
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
//创建对象的时候直接给里面的属性赋值
<bean id="hello" class="com.example.demospringbean.HelloWorld" scope="prototype" init-method="init" destroy-method="destroy">
<property name="message" value="asd" />
</bean>
//创建对象的时候直接给里面的属性赋值
<bean name="helloChina" class="com.example.demospringbean.HelloChina" parent="hello">
<property name="message2" value="Hello China"/>
</bean>
//<!--构造函数注入-->
<bean id="textEditor1" class="com.example.demospringbean.TextEditor">
<constructor-arg ref="spellChecker"/>
</bean>
// <!--Setter注入-->
<bean id="textEditor2" class="com.example.demospringbean.TextEditor" >
<property name="spellChecker" ref="spellChecker"/>
</bean>
<bean id="spellChecker" class="com.example.demospringbean.SpellChecker">
</bean>
// <!--嵌套bean Setter注入-->
<bean id="textEditor" class="com.example.demospringbean.TextEditor">
<property name="spellChecker">
<bean class="com.example.demospringbean.SpellChecker"/>
</property>
</bean>
//<!-- Definition for javaCollection -->
<bean id="javaCollection" class="com.example.demospringbean.JavaCollection">
//<!-- results in a setAddressList(java.util.List) call -->
<property name="addressList">
<list>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</list>
</property>
// <!-- results in a setAddressSet(java.util.Set) call -->
<property name="addressSet">
<set>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</set>
</property>
// <!-- results in a setAddressMap(java.util.Map) call -->
<property name="addressMap">
<map>
<entry key="1" value="INDIA"/>
<entry key="2" value="Pakistan"/>
<entry key="3" value="USA"/>
<entry key="4" value="USA"/>
</map>
</property>
// <!-- results in a setAddressProp(java.util.Properties) call -->
<property name="addressProp">
<props>
<prop key="one">INDIA</prop>
<prop key="two">Pakistan</prop>
<prop key="three">USA</prop>
<prop key="four">USA</prop>
</props>
</property>
</bean>
</beans>
我们可以看到,在生成对象的时候,我们使用下面这个语句:
```javascript
TextEditor te1 = (TextEditor) context.getBean("textEditor1");
在getBean后面的这个字符串里面的内容就是XML配置文件中的bean id, 也就是和以下代码对应:
<!--构造函数注入-->
<bean id="textEditor1" class="com.example.demospringbean.TextEditor">
<constructor-arg ref="spellChecker"/>
</bean>
以上具体所述的例子是使用构造函数注入,另外一种如上图所显示,也就是setter方法注入。
具体解析代码
现在我们把以上代码进行拆解分析,全部分析清楚之后,Spring框架中关于对象的建立,也就是所谓的IOC和DI,就应该有一个直接明了的了解了,我们采取从主程序一步步往里深入的分析方法:
主程序中,我们首先引入这三个非常重要也是必要的包:
package com.example.demospringbean;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
原因是我们需要使用这两个包来建立一个容器。在这个程序中建立容器对象,对象名为context。
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
这样我们就可以用context对象里面getBean这个方法来生成对象了。
如果我们追踪getBean这个方法,我们会发现getBean这个方法是在AbstractApplicationContext abstract class下面的一个override了的一个方法。以下是源码:
//---------------------------------------------------------------------
// Implementation of BeanFactory interface
//---------------------------------------------------------------------
@Override
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}
如果我们要创建一个对象,并且这个对象类里面的属性类型为基本类型或者是String的话:
然后如果我们想建立一个对象的话,我们可以这样:
HelloWorld objA = (HelloWorld) context.getBean("hello");
创建AbstractApplicationContext 的实现类的对象和使用context.geBean来创建对象的。
这个getBean后面的String类型的参数“hello”指向的是XML配置文件中 id = “hello”的那个配置语句,也就是下面这段话:
//创建对象的时候直接给里面的属性赋值
<bean id="hello" class="com.example.demospringbean.HelloWorld" scope="prototype" init-method="init" destroy-method="destroy">
<property name="message" value="asd" />
</bean>
然后这个XML配置文件中, class=“com.example.demospringbean.HelloWorld”, 这句话是实际上和真正的HelloWorld类关联起来的语句。下一行中,property name=“message” value=“asd” ,指的就是在生成这个对象的时候,这个对象的属性中,我们把属性名为message的属性的值设为“asd”。(也就是说,在HelloWorld类里面,其中有一个属性的属性名为property)
如果我们要创建一个对象,并且这个对象类里面的属性类型为一个对象类的话:
然后我们可以看到,以下是配置文件中的部分具体内容
<!--构造函数注入-->
<bean id="textEditor1" class="com.example.demospringbean.TextEditor">
<constructor-arg ref="spellChecker"/>
</bean>
<!--Setter注入-->
<bean id="textEditor2" class="com.example.demospringbean.TextEditor" >
<property name="spellChecker" ref="spellChecker"/>
</bean>
<bean id="spellChecker" class="com.example.demospringbean.SpellChecker">
</bean>
这时候我们看到主程序,
TextEditor te1 = (TextEditor) context.getBean(“textEditor1”);
和之前讲到的一样,我们需要去XML文件里面找到id = textEditor1 的XML语句。在XML配置文件中我们发现,constructor-args 这个语句,说明在这里的注入方法是使用构造函数,也就是构造器注入,后面class=“com.example.demospringbean.TextEditor”,就是和具体的类名为TextEditor的类关联起来。
下图是TextEditor类:
package com.example.demospringbean;
public class TextEditor {
private SpellChecker spellChecker;
public TextEditor(){};
//构造器注入
public TextEditor(SpellChecker spellChecker) {
System.out.println("Inside TextEditor constructor." );
this.spellChecker = spellChecker;
}
//Setter注入
public void setSpellChecker(SpellChecker spellChecker) {
System.out.println("Inside setSpellChecker." );
this.spellChecker = spellChecker;
}
public SpellChecker getSpellChecker() {
return spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}
然而下面还有一句话: constructor-arg ref=“spellChecker”, 这个又是什么意思呢?
这个时候我们发现,TextEditor类的构造器里面除了无参数构造器以外,还有一个以SpellChecker类的对象为参数的构造器,那么如果我们想利用这个构造器来创建TextEditor的对象的话,这时候就要用到这个ref="spellChecker"了。并且注意: 这里的双引号里面的spellChecker 因为是ref = 的值,所以对应的其实是XML配置文件中的id = “spellChecker”的语句,如下图:
<bean id="spellChecker" class="com.example.demospringbean.SpellChecker">
其实对应的是这句话,所以这句话才是实际上把XML配置文件和真正的SpellChecker类关联起来的语句。
setter注入如上图,其实和构造器注入类似,区别就是:
构造器注入的语句是:constructor-arg
setter注入的语句是:property
总结
所以我们发现,实际上所谓的IOC和DI,其实就是一种编程中对象创建的思路,在Spring中广泛使用而已,并且XML注入中的构造器注入和setter注入,在单纯的代码结构上其实非常类似。在上述图片的demo中,还可以看到嵌套bean的注入还有javaCollection(有个自己创建的集合类)的注入,但是其实都是以setter为基础的一些拓展的注入方法,记住:只要看到XML配置文件中的注入方法中,只要是有property这个字段,就是以setter注入,至少是以setter注入为基础的注入(因为例如javaCollection(自己创建的一个集合类)的注入,注入的东西就是一个对象中包含集合,需要给集合里面赋值)。
所以通过以上示例,我们可以就可以比较清晰明确的看出来Spring中的对象的生成和属性的赋值是如何操作的。
我们也可以理解为,我们平时建立对象的时候,是主程序直接new一个类来建立对象,但是现在中间通过了Spring的XML配置文件当作中转站来建立。(或者可以通过注解的方式,注解的方式以后再讲)。