您的位置:首页 > 编程语言 > Java开发

读《Spring技术内幕》-总结3-IoC容器的初始化过程

2016-05-05 13:56 477 查看

博客概要

本篇只是结合编程式使用DefaultListableBeanFactory的代码,简单得描述了一下IOC容器初始化的过程,分为3个步骤:Resource定位,载入BeanDefinition,注册这些Bean 。以及Spring中初始化的过程和依赖注入的过程是分开的,只有在对bean设置过lazy-init的情况下可能会使依赖注入在容器初始化的时候就发生,文中也有详细说明原因。

IoC容器的初始化过程大概描述:

简单来说,IoC容器的初始化是由前面介绍的refresh()方法(在FileSystemXmlApplicationCOntext中的一个构造方法中有调用这个refresh()方法)来启动的,这个方法标志着IoC容器的正式启动。具体来说,这个启动包括 BeanDefinition的Resource定位(资源定位),载入,注册3个基本过程。在我们前面编程式的使用DefaultListableBeanFactory的时候,我们就可以清楚得看到Resource资源定位和载入过程的接口调用。

ClassPathResource res =new ClassPathResource("applications.xml");
DefaultListableBeanFactory factory =new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader =new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);


Spring把这3个过程分开,并且用不同的模块来完成,比如用相应的ResourceLoader,BeanDefinitionReader等模块,通过这样的设计方式,可以让用户更加灵活地根据自己的需要来对这些模块进行裁剪和添加

Resource资源定位过程

这个Resource定位指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,并且这个Resource**对各种形式的BeanDefinition都提供了统一的接口**。比如在文件系统中的BeanDefinition可以使用FileSystemResource来进行抽象,在类路径中的BeanDefinition可以使用前面提到的ClassPathResource来使用。

我的理解就是需要让程序定位到你的那个resource文件,比如我们常用的“application.xml”,里面定义了许多bean的信息,所以这个过程就是怎样让程序找到这个文件,然后还要将它封装成resource的类型(Resource是Spring用来封装I/O的类)。

BeanDefinition的载入

这个载入过程就是把我们定义好的Bean表示成IoC内部的数据结构–其实就是BeanDefinition。 具体来说,这个BeanDefinition就是POJO对象在IoC容器中的抽象,通过这个BeanDefinition定义的数据结构,使得IoC容器能够方便地对POJO对象,也就是Bean,进行管理。

向IoC容器注册这些BeanDefinition

接下来就是通过调用BeanDefinitionRegistry接口来把这些BeanDefinition向IoC容器中注册。下面是BeanDefinitionRegistry接口的源码:

public interface BeanDefinitionRegistry extends AliasRegistry {
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
//移除
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
//得到
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
//判断是否包含
boolean containsBeanDefinition(String beanName);
//返回所有在这个registry中定义的bean的名字,如果不存在就返回一个空数组
String[] getBeanDefinitionNames();
int getBeanDefinitionCount();
//判断指定的name是否已经在这个registry中使用,或者判断是否有一个bean的name或者是别名使用的是这个name
boolean isBeanNameInUse(String beanName);
}


观察左边的接口关系图:



可以看到经常提到的DefaultListableBeanFactory正是实现了该接口!

看看DefaultListableBeanFactory的代码(只摘取了一部分代码)

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {

private static final Map<String, Reference<DefaultListableBeanFactory>> serializableFactories =
new ConcurrentHashMap<String, Reference<DefaultListableBeanFactory>>(8);
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);

private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);

private volatile List<String> beanDefinitionNames = new ArrayList<String>(256);

private volatile Set<String> manualSingletonNames = new LinkedHashSet<String>(16);
.....
........
}


可以看到,在DefaultListableBeanFactory内部是通过将这些BeanDefinition放入HashMap中进行管理的。

需要注意的是,在这个IoC容器初始化的过程中,一般不包含Bean依赖注入的实现. 在Spring IoC的设计中,Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向IoC容器索取Bean的时候。但是也有例外,那就是设置了lazy-init值(延迟加载)的时候.

1. 首先说说lazy-init是干嘛的? 如果 lazy-init=”true” 表示我们需要这个Bean延迟加载,那么在IoC初始化的时候就不会实例化这个Bean,直到你索要的时候( getBean(String name) )才会实例化.如果没设置,默认是false ->不延迟加载,所以默认会初始化这些Bean,可能导致Spring启动速度很慢

2. 那么,如果有2个Bean:bean1,bean2。* bean1是延迟加载,bean2没有, bean2里面依赖bean1。 然后bean2在容器初始化的时候就实例化了,那么这会导致bean1也会实例化而不是延迟加载。*因为容器在实例化bean2的时候,如果bean1没有实例化,那bean2怎么依赖它,所以必须使得bean2也实例化。

因此,如果Ioc容器在启动的时候创建了那些设置为延迟实例化的bean的实例,你也不要觉得奇怪,因为那些延迟初始化的bean可能在配置的某个地方被注入到了一个非延迟初始化singleton bean里面。

如果两个bean:bean1,bean2,bean2依赖bean1,只要bean2没设置lazy-init(缺省是false)或者设置为lazy-init=false,那么它的依赖注入会在第一次调用bean2的时候才发生。只有bean2设置了为true,依赖注入就会在IoC初始化的时候发生

结论: 因此,在这个IoC容器初始化的过程中,一般是不包含Bean依赖注入的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: