Spring 启动流程,基于Xml配置启动做了什么?

Spring 3.0之前主要基于xml配置,它的启动流程中,做了些什么?

这里基于Spring5.0.8版本:对ClassPathXmlApplicationContext进行讲解,同时没有集成spring-web包,所以启动过程跳过servlet实现:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");

application.xml中的简单配置:

    <bean id="user" class="com.ytq.cloud.source.entity.User">
        <property name="name" value="zhangsan" />
        <property name="person" ref="person" />
    </bean>
    <bean id="person" class="com.ytq.cloud.source.entity.Person">
        <property name="name" value="lisi"/>
    </bean>
application.xml 只配置了一个Bean对象,以及它的属性name

在创建ClassPathXmlApplicationContext对象时,具体会做如下几件事情:

  1. 确定application.xml的位置,默认去项目路径下找:classpath:/applicaiton.xml
  2. 通过读取器(xxxReader)读取application.xml文件,将它读取到一个Resource对象中,Resource对象会保存application.xml文件的字节输入流(inputStream);
  3. xml文件在java中两种主要的解析方式是DOM解析,一个是SAX解析,Spring 中采用了DOM解析,来解析application.xml文件,将application.xml中的每一个<bean id="xxx" class="xxx">解析成AbstractBeanDefinition对象,AbstractBeanDefinition对象中包括Bean的属性,作用域,是否抽象,是否为懒加载等等属性信息;最后将解析的BeanDefinition对象保存到Map对象中(DefaultListableBeanFactory#registerBeanDefinition() 在这个方法中完成)
  4. 将我们解析的BeanDefinition对象取出,通过反射的方式对单例模式的BeanDefinition进行实例化,并保存在DefaultSingletonBeanRegistry对象的Map容易中(singletonObjects);

这里是启动的主线,中间Spring也做了大量的处理如:BeanFactory的创建于修饰,国际化处理,事件处理(ApplicationEvent),清除缓存等等;

1:资源文件读取与解析

  • 从ClassPathXmlApplicationContext对象的构造方法进入:

    public ClassPathXmlApplicationContext(
              String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
              throws BeansException {
              // 会在父类中创建一个资源解析器ResourcePatternResolver(可以理解为先买把刀,为劈柴做准备),它的实现类new PathMatchingResourcePatternResolver(this);
          super(parent);
          // 解析路径并保存到数组中
          setConfigLocations(configLocations);
          if (refresh) {
          // 启动过程的工作基本在refresh()方法中完成;
              refresh();
          }}
    
  • 进入refresh()方法,他的实现在AbstractApplicationContext类中,

    // 读取application.xml文件,并把它解析成BeanDefinition对象,然后注册到Map容器中;
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();               
    // 实例化剩余单例模式(非懒加载的)Bean对象
    finishBeanFactoryInitialization(beanFactory);
    
  • 进入obtainFreshBeanFactory()->refreshBeanFactory()

    // 创建BeanFactory子类实现DefaultListableBeanFactory
    DefaultListableBeanFactory beanFactory = createBeanFactory();
    //加载BeanDefinition
    loadBeanDefinitions(beanFactory);进入方法->AbstractXmlApplicationContext#loadBeanDefinitions
    方法中代码实现:
    reader.loadBeanDefinitions(configLocations); reader就是读取器,configxxx就是配置文件路径;
    转换为:XmlBeanDefinitionReader.loadBeanDefinitions(new String[]{"application.xml"});
    XmlBeanDefinitionReader是Spring读取配置文件的一个工具类;
    进行进入方法,这里有好几层方法,还是重载的loadBeanDefinitions
    {
      这个方法就是讲配置文件读取到Resource资源对象中,并保存着配置文件字节输入流inputstream
      其中resourceLoader资源加载器,就是ClassPathXmlApplicationContext构造方法中创建的PathMatchingResourcePatternResolver对象;然后在调用DefaultResourceLoader的getResource方法;
      Resource resource = resourceLoader.getResource(location);
      // XmlBeanDefinitionReader对Resource的读取与解析
      int count = loadBeanDefinitions(resource);
    }
    
  • 进入XmlBeanDefinitionReader对象的loadBeanDefinitions方法,

      // 对流进行编码与解码处理
      InputStream inputStream = encodedResource.getResource().getInputStream();
              try {
                  InputSource inputSource = new InputSource(inputStream);
                  if (encodedResource.getEncoding() != null) {
                      inputSource.setEncoding(encodedResource.getEncoding());
                  }
                  // 读取流文件;
                  return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
              }
          进入doLoadBeanDefinitions方法,这里就是通过DOM方法,对xml流进行解析的过程
          Document doc = doLoadDocument(inputSource, resource);
          // 通过doc读取里面的每一个element元素,继续进入方法;
          int count = registerBeanDefinitions(doc, resource);
    进入到->DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
    进入方法->parseBeanDefinitions(root, this.delegate);
    
  • 在parseBeanDefinitions解析方法中

    这里的root就是document根节点,获取它所有node节点进行遍历,
    NodeList nl = root.getChildNodes();
              for (int i = 0; i < nl.getLength(); i++) {
                  Node node = nl.item(i);
                  if (node instanceof Element) {
                      Element ele = (Element) node;
                      if (delegate.isDefaultNamespace(ele)) {
                      // 解析element元素节点,如果他是bean开头,就解析他的id,class,name等属性,封装到AbstractBeanDefinition对象中;
                          parseDefaultElement(ele, delegate);
      }}}
    
  • BeanDefinition的解析构成就此完成,最后将解析生成的AbstractBeanDefinition注册到DefaultListableBeanFactory(registerBeanDefinition方法实现注册过程)对象的Map容器中,key就是id定义名称,value就是AbstractBeanDefinition对象,注册的过程中,会判断beanName在同一个上下文中的唯一性,如果beanName重复,注册失败;

2.单例Bean的实例化

  • 进入AbstractApplicationContext的refresh()方法的finishBeanFactoryInitialization(beanFactory)方法:

    // 初始化所有非延迟加载的Bean实例,这里的BeanFactory就是上传创建的DefaultListableBeanFactory对象;
    进入方法->beanFactory.preInstantiateSingletons();
    {
          //这里就是取上面解析到的所有BeanDefinition的name名称,进行遍历
          List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
          for (String beanName : beanNames) {
              RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
              ...
              // 我们自己定义的普通bean,走这里实例化过程
              getBean(beanName);
    }}}
    进入方法-->AbstractBeanFacotory.doGetBean()方法
    {
    // 如果是单例模式就创建单例,如果是原型模式就创建原型模式实例,
    这里只复制了创建单例代码,
    if (mbd.isSingleton()) {
      return createBean(beanName, mbd, args);
    }}
    进入createBean方法
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    
    
  • 进入到AbstractAutowireCapableBeanFactory的doCreateBean方法,执行对象的创建于属性依赖注入

          // Bean对象的创建,根据beanName获取Class对象,通过Clazz.newInstance(args)创建bean对象;
        BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
        try {
          // 真正的依赖注入处理方法,在依赖注入时,同时会建立对象之间的依赖关系如:(A依赖B,C等),将这种依赖关系保存在Map容器中,key是beanName,Value是set集合依赖的所有对象;
          populateBean(beanName, mbd, instanceWrapper);
          // Bean初始化方法处理(@PostConstruct注解或者@Bean(init-method="xxx")),同时在初始化之前与之后做些功能扩展,初始化方法会在这里处理;
          exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
    
  • populateBean(beanName, mbd, instanceWrapper);依赖注入构成实现:

    获取bean的所有属性id与配置的属性值value,同时会解析value值的类型,
    BeanDefinitionValueResolver对象的resolveValueIfNecessary(pv, originalValue)方法就是判断value值是什么类型的值,value可能set集合,list,map,string,具体对象;
    spring提供了强大的类型转换器,将value转换成具体的类型,如果value引用了一个person对象
    1. 会先到Map容器中去获取person对象BeanFactory.getBean("person")如果person存在,通过反射调用User的setPerson(Person person)方法,进行设值。setter方法注入
                  ReflectionUtils.makeAccessible(writeMethod);
                  writeMethod.invoke(getWrappedInstance(), value);
    2. 如果person在IoC容器中不存在,会调用BeanFactory.createBean("person")方法去创建Person对象,然后通过反射调用setPerson()方法,进行注入;
    具体实现在BeanWrapperImpl的setValue()方法;
    Bean创建完成之后,DefaultSingletonBeanRegistry对象getSingleton方法中,把创建的Bean添加到Map容器中(singletonObjects),这里就是IOC容器管理所有单例Bean的地方;
    
  • 属性注入完成之后,调用initializeBean()初始化方法:

    protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
          ...
          Object wrappedBean = bean;
          if (mbd == null || !mbd.isSynthetic()) {
          // 初始之前通过BeanPostProcessor对Bean做修饰处理
              wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
          }
          // 初始化方法执行,@PostConstruct注解方法或者@Bean(init-method="initxxx")方法
          invokeInitMethods(beanName, wrappedBean, mbd);
          if (mbd == null || !mbd.isSynthetic()) {
          //  初始之后通过BeanPostProcessor对Bean做修饰处理,这里在Spring注解Annotation启动过程中起了很重要的作用,后面将Spring Annotation启动会说
              wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
          }
          return wrappedBean;
      }
    

Spring IoC容器的初始化过程基本就完成了;

它包括配置文件读取载入,解析,Bean创建,Bean属性注入几个流程;

后面在完善!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容