IoC,Inversion of Control,控制反转。控制反转是一种通过描述(在java中可以使XML或者注解)并通过第三方去获取特定对象的方式。简单地说就是将对象由虚拟机主动创建变为从IoC容器中获取,它是面向对象编程的一种思想,主要作用是降低开发难度、对模块解耦、有利于测试
一、内部设计
BeanFactory作为最顶层的一个接口类,它定义了IOC容器的基本功能规范。
BeanFactory的子接口HierarchicalBeanFactory是一个具有层级关系的Bean 工厂,拥有属性parentBeanFactory。当获取 Bean对象时,如果当前BeanFactory中不存在对应的bean,则会访问其直接 parentBeanFactory 以尝试获取bean 对象。此外,还可以在当前的 BeanFactory 中 override 父级BeanFactory的同名bean。
ListableBeanFactory 继承了BeanFactory,实现了枚举方法可以列举出当前BeanFactory中所有的bean对象而不必根据name一个一个的获取。 如果 ListableBeanFactory 对象还是一个HierarchicalBeanFactory则getBeanDefinitionNames()方法只会返回当前BeanFactory中的Bean对象而不会去父级BeanFactory中查询。
ApplicationContext接口继承众多接口,集众多接口功能与一身,为Spring的运行提供基本的功能支撑。根据程序设计的“单一职责原则”,其实每个较顶层接口都是“单一职责的”,只提供某一方面的功能,而ApplicationContext接口继承了众多接口,相当于拥有了众多接口的功能,
-
它是个BeanFactory,可以管理、装配bean,可以有父级BeanFactory实现Bean的层级管理(具体到这里来说它可以有父级的ApplicationContext,因为ApplicationContext本身就是一个BeanFactory。这在web项目中很有用,可以使每个Servlet具有其独立的context, 所有Servlet共享一个父级的context),它还是Listable的,可以枚举出所管理的bean对象。(实现了ListableBeanFactory, HierarchicalBeanFactory接口)
-
支持信息源,可以实现国际化。(实现MessageSource接口)
-
访问资源。(实现ResourcePatternResolver接口,这个后面要讲)
-
支持应用事件,可以发布事件给注册的Listener,实现监听机制。(实现ApplicationEventPublisher接口)
二、IOC容器初始化的基本步骤:
1、设置资源加载器和资源定位
ApplicationContext ac = new FileSystemXmlApplicationContext(xmlPath);
2、refresh函数载入Bean定义
refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
prepareRefresh();
//告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从
//子类的refreshBeanFactory()方法启动
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//为BeanFactory配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
//为容器的某些子类指定特殊的BeanPost事件处理器
postProcessBeanFactory(beanFactory);
//调用所有注册的BeanFactoryPostProcessor的Bean
invokeBeanFactoryPostProcessors(beanFactory);
//为BeanFactory注册BeanPost事件处理器.
//BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件
registerBeanPostProcessors(beanFactory);
//初始化信息源,和国际化相关.
initMessageSource();
//初始化容器事件传播器.
initApplicationEventMulticaster();
//调用子类的某些特殊Bean初始化方法
onRefresh();
//为事件传播器注册事件监听器.
registerListeners();
//初始化所有剩余的单态Bean.
finishBeanFactoryInitialization(beanFactory);
//初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}
catch (BeansException ex) {
//销毁以创建的单态Bean
destroyBeans();
//取消refresh操作,重置容器的同步标识.
cancelRefresh(ex);
throw ex;
}
}
}
在refresh方法中ConfigurableListableBeanFactorybeanFactory = obtainFreshBeanFactory();启动了Bean定义资源的载入、注册过程:
(1). 资源加载器获取要读入的资源
(2). 加载Bean定义资源
(3). 将Bean定义资源转换为Document对象
(4). 对Bean定义的Document对象解析,包括、以及它的子元素,其中ref被封装为指向依赖对象一个引用,而value配置都会封装成一个字符串类型的对象。
(5). 解析过后的BeanDefinition会注册到IoC容器中。
BeanDefinition是Bean的元数据信息,其中囊括了一个Bean对象的所有信息:
(1). Bean的类型
(2). Bean的属性值
(3). Bean的构造参数值
(4). Bean的属性访问
(5). 等等…
注册过程就是在 IOC 容器内部维护的一个HashMap 来保存得到的 BeanDefinition 的过程。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
Bean定义资源的载入之后代码都是注册容器的信息源和生命周期事件。而finishBeanFactoryInitialization方法是对注册后的Bean定义中的预实例化(lazy-init=false,Spring默认就是预实例化,即为true)的Bean进行处理的地方。
三、IOC容器的依赖注入
1、依赖注入发生的时间
当Spring IoC容器完成了Bean定义资源的定位、载入和解析注册以后,IoC容器中已经管理类Bean定义的相关数据,但是此时IoC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况发生:
(1).用户第一次通过getBean方法向IoC容索要Bean时,IoC容器触发依赖注入。
(2).当用户在Bean定义资源中为元素配置了lazy-init属性,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。
2、依赖注入的实现
构造器注入:依赖于构造方法实现,构造方法可以使有参的和无参的。Spring内部采用反射的方式调用构造方法实现注入。XML方式对应c标签。
设值注入:设值注入依赖setter方法,是Spring最主流的注入方式。灵活且可读性高。其原理也是通过反射实现,设值注入依赖于无参构造器,当bean类声明了带参构造器时必须同时声明无参构造器。XML方式对应p标签。
接口注入:适用于来自于外界的资源,比如数据库连接资源可以在Tomcat下配置,然后通过JNDI的形式去获取它。此时可以使用借口注入。
3、关于循环依赖
2个或以上bean 互相持有对方,最终形成闭环,这样就造成了循环依赖。
Spring为了解决单例的循环依赖问题,使用了三级缓存。
(1). singletonFactories : 单例对象工厂的cache
(2). earlySingletonObjects :提前暴光的单例对象的Cache 。【用于检测循环引用,与singletonFactories互斥】
(3). singletonObjects:单例对象的cache
Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或zh属性是可以延后设置的(但是构造器必须是在获取引用之前,因此构造器注入引起的循环依赖无法解决)。
Spring是先将Bean对象实例化【依赖无参构造函数】然后再设置对象属性的,在实例化Bean后,会先将该对象放到三级缓存singletonFactories中,当产生循环依赖的时候,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。这时循环以来的问题就解决了。
四、关于FactoryBean
FactoryBean:工厂Bean,是一个Bean,作用是产生其他bean实例。通常情况下,这种bean没有什么特别的要求,仅需要提供一个工厂方法,该方法用来返回其他bean实例。通常情况下,bean无须自己实现工厂模式,Spring容器担任工厂角色;但少数情况下,容器中的bean本身就是工厂,其作用是产生其它bean实例。
BeanFactory:Bean工厂,是一个工厂(Factory),我们Spring IoC容器的最顶层接口就是这个BeanFactory,它的作用是管理Bean,即实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
在BeanFactory的getBean()实例化对象的时候会使用BeanFactory的实现类getObject方法创建Bean实例对象。
当用户使用容器本身时,可以使用转义字符”&”来得到FactoryBean本身,以区别通过FactoryBean产生的实例对象和FactoryBean对象本身。在BeanFactory中通过如下代码定义了该转义字符:
StringFACTORY_BEAN_PREFIX = "&";
如果myJndiObject是一个FactoryBean,则使用&myJndiObject得到的是myJndiObject对象,而不是myJndiObject产生出来的对象。
public interface FactoryBean<T> {
//获取容器管理的对象实例
T getObject() throws Exception;
//获取Bean工厂创建的对象的类型
Class<?> getObjectType();
//Bean工厂创建的对象是否是单态模式,如果是单态模式,则整个容器中只有一个实例
//对象,每次请求都返回同一个实例对象
boolean isSingleton();
}
五、BeanPostProcessor后置处理器的实现
BeanPostProcessor后置处理器是Spring IoC容器经常使用到的一个特性,这个Bean后置处理器是一个监听器,可以监听容器触发的Bean声明周期事件。后置处理器向容器注册以后,容器中管理的Bean就具备了接收IoC容器事件回调的能力。
public interface BeanPostProcessor {
//为在Bean的初始化前提供回调入口
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
//为在Bean的初始化之后提供回调入口
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
BeanPostProcessor是一个接口,其初始化前的操作方法和初始化后的操作方法均委托其实现子类来实现,在Spring中,BeanPostProcessor的实现子类非常的多,分别完成不同的操作,如:AOP面向切面编程的注册通知适配器、Bean对象的数据校验、Bean继承属性/方法的合并等等
BeanPostProcessor的使用非常简单,只需要提供一个实现接口BeanPostProcessor的实现类,然后在Bean的配置文件中设置即可。
六、IOC的简单实现
最简单的 IOC 容器只需4步即可实现,如下:
-
加载 xml 配置文件,遍历其中的标签
-
获取标签中的id,加载class属性对应的类,并创建 bean
-
遍历标签中的标签,获取属性值,并将属性值填充到 bean 中
-
将 bean 注册到 bean 容器中
要注入的Bean
public class Car {
private String name;
private String length;
private String width;
private String height;
private Wheel wheel;
// get set...
}
public class Wheel {
private String brand;
private String specification;
}
容器实现类:
public class SimpleIOC {
private Map<String, Object> beanMap = new HashMap<>();
public SimpleIOC(String location) throws Exception {
loadBeans(location);
}
public Object getBean(String name) {
Object bean = beanMap.get(name);
if (bean == null) {
throw new IllegalArgumentException("there is no bean with name " + name);
}
return bean;
}
private void loadBeans(String location) throws Exception {
// 加载 xml 配置文件
InputStream inputStream = new FileInputStream(location);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document doc = docBuilder.parse(inputStream);
Element root = doc.getDocumentElement();
NodeList nodes = root.getChildNodes();
// 遍历 <bean> 标签
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
String id = ele.getAttribute("id");
String className = ele.getAttribute("class");
// 加载 beanClass
Class beanClass = null;
try {
beanClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
// 创建 bean
Object bean = beanClass.newInstance();
// 遍历 <property> 标签
NodeList propertyNodes = ele.getElementsByTagName("property");
for (int j = 0; j < propertyNodes.getLength(); j++) {
Node propertyNode = propertyNodes.item(j);
if (propertyNode instanceof Element) {
Element propertyElement = (Element) propertyNode;
String name = propertyElement.getAttribute("name");
String value = propertyElement.getAttribute("value");
// 利用反射将 bean 相关字段访问权限设为可访问
Field declaredField = bean.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
if (value != null && value.length() > 0) {
// 将属性值填充到相关字段中
declaredField.set(bean, value);
} else {
String ref = propertyElement.getAttribute("ref");
if (ref == null || ref.length() == 0) {
throw new IllegalArgumentException("ref config error");
}
// 将引用填充到相关字段中
declaredField.set(bean, getBean(ref));
}
// 将 bean 注册到 bean 容器中
registerBean(id, bean);
}
}
}
}
}
private void registerBean(String id, Object bean) {
beanMap.put(id, bean);
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="wheel" class="spring.ioc.Wheel">
<property name="brand" value="Michelin" />
<property name="specification" value="265/60 R18" />
</bean>
<bean id="car" class="spring.ioc.Car">
<property name="name" value="Mercedes Benz G 500" />
<property name="length" value="4717mm" />
<property name="width" value="1855mm" />
<property name="height" value="1949mm" />
<property name="wheel" ref="wheel" />
</bean>
</beans>
测试方法
public class SimpleIOCTest {
@Test
public void getBean() throws Exception {
System.out.println(SimpleIOC.class.getClassLoader().getResource(""));
String location = SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile();
SimpleIOC bf = new SimpleIOC(location);
Wheel wheel = (Wheel) bf.getBean("wheel");
System.out.println(wheel);
Car car = (Car) bf.getBean("car");
System.out.println(car);
}
}
参考
https://www.cnblogs.com/zffenger/p/5813470.html
https://www.cnblogs.com/ITtangtang/p/3978349.html
https://blog.csdn.net/qq_36381855/article/details/79752689
https://www.jb51.net/article/140754.htm
http://www.tianxiaobo.com/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-上篇/