三.IOC启动流程分析法
我们从ClasspathXmlApplicationContext的构造方法说起
3,1:调用父类的构造方法,进行相关的对象创建工作,跟踪到父类AbstractApplicationContext,看到首先创建了一个资源式解析器PathMatchingResourcePatternResover,它是用来解析XML的,后面会用到。
3.2:setConfigLocations,设置配置文件的路径,也就是将classpath:spring-config.xml设置到configLocations成员变量中,后面读取XML配置文件时会用到。setConfigLocations内部还调用了resolvePath()方法,主要完成了Enviroment环境对象的创建,以及配置文件中的${xxx}占位符的替换操作。当执行resolvePath()方法里的new StandardEnviroment()构造方法时,首先会调用父类的AbbstractEnviroment的构造方法,留意到customizePropertySource()方法是空实现的,那么就看子类StandardEnviroment是否有重写customizePropertySource()方法。果然StandardEnviroment重写了额customizePropertySource()方法,里面执行了一个很关键的逻辑:通过System.getProperties()读取我们系统的属性,通过Ssytem.getenv()获取系统变量,并把他们添加到系统属性里面,后面就可以直接拿出来。getEnviroment()方法执行完后,接着调用resolveRequiredPlaceholders(path),这个方法的作用是解析文本中的${xxx}占位符,例如${db.username},${db.driverClass}等占位符,spring就会将他们解析成具体的值。
3.3:调用refresh()方法,这是整个IOC容器的核心。里面总共有12个方法,之后会详细分析。为什么要是refresh()方法,而不是init()呢,因为refresh()会把之前的的ApplicationContext销毁,然后再重新执行一次初始化操作。
接下来12个方法重点逐一分析:
四.prepareRefresh():创建bean容器前的准备工作。
主要完成容器刷新前的一些准备工作,如设置启动时间,一些标志位等。另外,我们也可以通过重写initPropertySource()方法实现校验一些属性的必填
五.obtainFreshBeanFactory():创建bean容器
该方法是IOC容器中一个重要的方法,主要创建了一个IOC容器DefaultListableBeanFactroy,通过读取XML配置文件,并解析封装成一个个BeanDefinition(bean定义信息),然后注册到Bean工厂中。主要关注refreshBeanFactory()方法。
5.1:obtainFreshBeanFactory()第一步:销毁之前已经存在的bean工厂以及对应的bean。
5.2:obtainFreshBeanFactory()第二步:创建DefaultListableBeanFactory bean工厂。通过createBeanFactory()方法创建。留意一下BeanFactory两个重要的属性,BeanDefinition定义信息和beanName集合。
private final Map<String,BeanDefinition> beanDefinition=new ConcurrentHashMap<>(256);
private volatile List<String> beanDefinitionNames=new ArrayList<>(256);
5.3:obtainFreshBeanFactory()第三步:定制化bean工厂
通过customizeBeanFactory()方法定制化bean工厂,也就是设置两个属性值而已:
是否允许覆盖同名称的不同定义的对象;
四否允许循环依赖;
对于这个属性值,不为空的时候才进行设置。
同样我们可以自定义ClassPathXmlApplicationContext来定制话设置这两个属性值。
5.4:obtainFreshBeanFactory()第四步:读取,解析配置文件,加载bean定义一级注册bean。o
refreshBeanFactororyy最后一步,也是最重要的一个环节,调用loadBeanDefinitions(beanFact)方法,主要完成XML配置文件的读取,解析以及向bean工厂注册bean的工作.
首先为给定的BeanFactory创建一个配置文件读取器XmlBeanDefinitionReader,接着设置一些beanDefinitonReader属性,如环境对象,资源加载器等,最后通过BeanDeginitionReader加载bean定义信息。其中因为配置文件可能存在多个,所以需要循环进行读取。而后,通过ResourcePatternResolver将配文件的路径转化为具体的Resource后,又调用loadBeanDefinitions(resources)重载方法,通过resource来加载bean定义信息。上述主要的工作其实就是从encodedResoure中获取已经封装好的Resource对象,再根据Resource对象获取对应的输入流,前面一系列都是准备工作,接下来是重点读取过程方法doLoadBeanDefinitions(inputSource,encodeResource.getResource()。
doLoadBeanDefinitions()方法主要完成了两件事请:
1.doLoadDocument(inputSoure,resource):将XML配置文件转化为Document文档对象,由documentLoader完成,spring使用SAX的方式去解 析XML文件,封装成Document文档对 象;
2.registerBeanDefinitons(doc,resource):从document文档解析,注册bean定义信息。
接着主要看看registerBeanDefinitons方法:
首先创建了一个创建文档读取器对象,然后通过读取器加载及注册bean。继续追踪,可以看到doRegisterBeanDefinitions(Element root)方法,主要逻辑在最后三个方法:
1.preProcessXml():留给子类扩展,可以进行解析XML前置一些工作
2.parseBeanDefinitons():解析bean定义,具体解析各个标签的方法
3.postProcessXml():留给子类扩展,可以进行解析XML后置的一些处理
下面来分析parseBeanDefinitions()方法:要解析分为两种:
1.默认命名空间解析:对默认命名空间的解析过程,主要针对<import/>,<alias/>,<bean/>,<beans/>标签进行解析
2.自定义命名空间解析:自定义标签的解析过程
对bean标签的解析非常重要,理解了bean标签的解析过程就理解了其他标签的解析过程,主要看一下processBeanDefinitions()方法:委托BeanDefinitionParseDelegate进行元素的解析,返回BeanDefinitionHolder实例的bdHolder,经过这个方法后,bdHolder实例已经包含我们配置文件中各种属性了,如class,id,name等属性。继续看parseBeanDefiitionElement()方法。可以看到Spring解析bean标签中一系列常用的属性。
第一步:创建用于属性承载的BeanDefinition;
第二步:解析bean标签的其他一些属性,如autowired,singleton,layy-init,scop,primary
第三步:解析meta标签,构造方法参数,property子元素等信息、
到这里,配置文件的解析已经完成,剩下的就是把它注册到工厂中。Spring通过BeanDefinitionReaderUtils.registerBeanDefinition()来实现bean定义信息的注册。解析的XML,会将对应的信息封装成BeanDefinition对象,然后注册到BeanDefinitionRegistry类型的实例registry中。对于注册BeanDefinition,分为了两个部分:
1.beanName的注册
2.通过别名的注册
最后,就将这个bean定义信息注册到了工厂中
六.prepareBeanFactory(beanFactory):准备bean工厂,设置类加载器,添加一些BeanPostProcessor等
七.postProcessBeanFactory(beanFactory):留给子类对BeanFactory定制化处理。
Spring中没有具体去实现postProecssBeanFacory()方法,他是一个模版方法,留给子类去拓展,作用是在BeanFactory准备工作完成后做一些定制化的处理,一般结合BeanPostProcess接口的实现类一起使用,如注入一些重要资源等