Spring有两大核心特性 IOC和AOP,今天我们来聊的是IOC相信很多同学每天都在使用Spring的IOC特性,但对其原理及实现理解比较模糊,那么今天就借助Spring容器实现原理,简单说说IOC说到IOC 就涉及到两个概念控制反转(Inversion of Control)、依赖注入(Dependency Injection),下面我们就来聊聊关于什么是springioc容器及其优点?接下来我们就一起去了解一下吧!
什么是springioc容器及其优点
Spring有两大核心特性 IOC和AOP,今天我们来聊的是IOC。相信很多同学每天都在使用Spring的IOC特性,但对其原理及实现理解比较模糊,那么今天就借助Spring容器实现原理,简单说说IOC。说到IOC 就涉及到两个概念控制反转(Inversion of Control)、依赖注入(Dependency Injection)。
首先来看什么是控制反转
在介绍控制反转之前呢我们看一段最早的代码
Public class PersonServiceBean{
PrivatePersonDaoBeanpersonDao=newPersonDaoBean();
Publicvoidsave(Personperson){
personDao.insert(person);
}
}
PersonDaoBean 是在应用内部创建及维护的。所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的。这样控制反转权就由应用转到了外部容器,控制权的转移就是所谓反转。
用Spring的写法:把依赖对象交给外部容器去负责
Public class PersonServiceBean{
PrivatePersonDaoBeanpersonDao;
//把容器创建好的对象依赖注入进PersonServiceBean,通过构造方法或set方法
PublicPersonServiceBean(PersonDaoBeanpersonDao){
this.personDao=personDao;
}
或
PublicvoidsetPersonDao(PersonDaoBeanpersonDao){
this.personDao=personDao;
}
Publicvoidsave(Personperson){
personDao.insert(person);
}
}
其次来看什么是依赖注入
所谓依赖注入:就是在运行期间,由外部容器动态的将依赖对象注入到组件中。
简单的说,Spring就是通过工厂 反射将我们的bean放到它的容器中,当我们想用某个bean的时候,只需要调用 getBean("beanId")方法。
依赖注入的方式:
第一.构造器注入;
第二.set方法注入;
第三.使用Field注入(用于注解方式)
然后我们写一段简单的模拟Spring的代码来实现IOC容器
思路:Spring容器的原理实现主要依赖于反射。过程其实就是通过解析xml文件,获取到用户配置的bean,然后通过反射将这些bean进行存储(放到集合中),然后对我提供一个getBean方法,以便我们获取到这些bean。下面是一段简单的模拟代码:
package com.spring.factory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;
public class ClassPathXmlApplicationContext implements BeanFactory {
//容器的核心,用来存放注入的Bean
private Map<String, Object> container = new HashMap<String, Object>();
//解析xml文件,通过反射将配置的bean放到container中
public ClassPathXmlApplicationContext(String fileName) throws Exception {
SAXBuilder sb = new SAXBuilder();
Document doc = sb.build(this.getClass().getClassLoader().getResourceAsStream(fileName));
Element root = doc.getRootElement();
List list = XPath.selectNodes(root, "/beans/bean");
//扫描配置文件中的bean
for (int i = 0; i < list.size(); i ) {
Element bean = (Element) list.get(i);
String id = bean.getAttributeValue("id");
String clazz = bean.getAttributeValue("class");
//反射
Object o = Class.forName(clazz).newInstance();
container.put(id, o);
}
}
@Override
public Object getBean(String id) {
return container.get(id);
}
}
说明:首先声明一个存放bean的Map,然后通过jdom解析配置文件,循环遍历所有的节点,并通过反射将它们放到我们之前声明的Map中.然后提供一个getBean的方法,让我们可以通过beanId来获取到我们想要的bean。
下面是一个简单的xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<beanid="E"class="com.spring.factory.England"/>
<beanid="S"class="com.spring.factory.Spain"/>
<beanid="P"class="com.spring.factory.Portugal"/>
</beans>
客户端通过调用前面的ClassPathXmlApplicationContext,来加载上面的配置文件,然后就可以通过Id来获得我们需要的bean了:
packagecom.spring.factory;
public class Test {
public static void main(String[] args) throws Exception {
//加载配置文件
BeanFactory f = new ClassPathXmlApplicationContext("applicationContext.xml");
//英格兰
Object oe = f.getBean("E");
Team e = (Team)oe;
e.say();
//西班牙
Object os = f.getBean("S");
Team s = (Team)os;
s.say();
//葡萄牙
Object op = f.getBean("P");
Team p = (Team)op;
p.say();
}
}
输出结果:
England:我们是欧洲的中国队,不在乎这次小组没出线...
Spain :我们是两届欧洲杯冠军、一届世界杯冠军!
Portugal:我们的C罗一个顶十个!
其它代码:
//工厂接口
package com.spring.factory;
public interface BeanFactory {
Object getBean(String id);
}
//Team接口
package com.spring.factory;
public interface Team {
void say();
}
//英格兰
packagecom.spring.factory;
public class England implements Team{
public void say() {
System.out.println("England:我们是欧洲的中国队,不在乎这次小组没出线...");
}
}
//西班牙
packagecom.spring.factory;
public class Spain implements Team{
@Override
public void say() {
System.out.println("Spain:我们是两届欧洲杯冠军、一届世界杯冠军!");
}
}
//葡萄牙
packagecom.spring.factory;
public class Portugal implements Team {
@Override
public void say() {
System.out.println("Portugal:我们的C罗一个顶十个!");
}
}
以上内容是对Spring的一个简单模拟,当然Spring远比这个要复杂的多,也强大的多,而且获取bean的方式也不止通过工厂这一种。这里只是做一个粗略的Demo说说对容器的简单理解,向Spring致敬。
扩展,那么如果有 property属性的bean该如何初始化呢?(依赖注入)
如xml配置方式:<property name="dataSource" ref="datas"/> bean有 property属性, 包含name和ref。
这里提供思路,感兴趣的同学可以自行实现,也可参看spring的源码。
思路:
1. 获取Bean的set方法 setA --- 反射
2. 获取Bean的声明的属性.equals(propertyDefine.getName())
3. 获取Bean的ref 通过ref作为Key从Map中获取到实例对象
4. setA.invoke(Bean,实例对象) --- 反射
整个原理大致分三步:
1.读取xml文件
2.实例化bean 保存到map中
3.为有property 的Bean注入Bean0对象
说了这么多,那么IOC有什么好处呢
降低组件的耦合,使各层解耦。bean对象交于容器管理。
小结
相信读完此篇会对Spring IOC的概念和实现原理更加清晰。
控制反转:就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器Spring负责。
依赖注入:由外部容器Spring动态的将依赖对象注入到组件中。
实现原理:JAVA反射机制。
,