Springboot启动流程4 - run方法【3】准备上下文

147 阅读7分钟

3.准备上下文

看了这么多和参数配置以及外围相关的代码,到这里终于要开始看和核心的context相关的代码了。

这里我们只看主流程的这一行代码:

prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

这里的代码,对应的方法具体如下:

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   applyInitializers(context);
   listeners.contextPrepared(context);
   bootstrapContext.close(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   // Load the sources
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[0]));
   listeners.contextLoaded(context);
}

3.1 继续完善上下文

这里涉及代码如下:

context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);

所做的工作是围绕完善context中相关属性的。

3.1.1 配置环境

第一步是把environment放到context里面,这个行为也好理解,这样子在上下文这个定义上,context对象包括的信息也就更完整了,后面要使用environment信息的时候直接从context里面取,也减少了参数的传递。

context.setEnvironment(environment);

3.1.2 前置处理context

postProcessApplicationContext(context);

	protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
		if (this.beanNameGenerator != null) {
			context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
					this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			if (context instanceof GenericApplicationContext) {
				((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
			}
			if (context instanceof DefaultResourceLoader) {
				((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
			}
		}
		if (this.addConversionService) {
			context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());
		}
	}

在这一步中,对context进行了context一些关键属性的注入,到这里context才算是开始具有了一些context应有的核心功能。

这里遇到了一个问题:

  • 其实在我们上面创建context的时候,其实调用的只是:

    new AnnotationConfigServletWebServerApplicationContext(),对应的构造方法如下:

    public AnnotationConfigServletWebServerApplicationContext() {
      this.reader = new AnnotatedBeanDefinitionReader(this);
       this.scanner = new ClassPathBeanDefinitionScanner(this);
    }
    

这里跟beanFactory没有一分钱关系,那么为什么到了注册这里的getBeanFactory可以拿到东西?

  • 而且,如果我们在构造方法中打断点,可以知道在第一行之前,getBeanFactory已经可以拿到东西了。

这里就是继承的一个特点了:

  • 创建一个子类对象时,会调用父类的构造方法,即使没有super关键字

此时根据继承关系上溯,在GenericApplicationContext的构造方法中发现了:

public GenericApplicationContext() {
   this.beanFactory = new DefaultListableBeanFactory();
}

这也就解释了这里的beanFactory哪里来的,我们也清楚这个是哪个类了。

这里Spring中,第二个核心类出现了:

  • BeanFactory

我们来解析DefaultListableBeanFactory这个类。

继承关系:见图www.processon.com/view/link/6…

根据类图很容易地判断出这里的registerSingleton对应的代码如下:

public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
   super.registerSingleton(beanName, singletonObject);
   updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName));
   clearByTypeCache();
}

在额外信息中,我们再来重点关注这个代码。

回到主流程:

主流程中,这里根据配置开关情况,至多:

  • 在beanFactory中注册了一个单例bean:beanNameGenerator

    • 由于我们这里设置是null,因此这一步并不会触发
  • 为context设置资源装载器

  • 为context设置类加载器

  • 根据设置,为beanFacotry设置ConversionService

    • 这里其实就是我们之前放到context里的环境对象的转换服务,对应的类其实是ApplicationConversionService

3.1.3 设置初始化器

实际上这里就是将之前在构造方法中配置的initializer,分别判断并执行初始化的操作。

  • 这里指的初始化,并不是构造方法,而是接口的实现。

这里就显示出设计上下文context这一对象的好处了:只要拥有了这一context对象,那么无论是哪个地方都可以知道当前应用的大部分信息,而不需要根据需要在方法中定义不同的入参,这里就是框架的设计之一:统一处理

  • 就比如这里的initializer,拥有了context对象就知道了需要的所有信息,只要按需要取即可。
protected void applyInitializers(ConfigurableApplicationContext context) {
   for (ApplicationContextInitializer initializer : getInitializers()) {
      Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
            ApplicationContextInitializer.class);
      Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
      initializer.initialize(context);
   }
}

3.2 外部通知相关步骤

listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
   logStartupInfo(context.getParent() == null);
   logStartupProfileInfo(context);
}

这里实际上是发了一些通知,以及日志的打印。

  • listener中定义了若干接口,这个也是一样的,就不多做赘述
  • 而bootstrapContext实际上也是相同的,根据事件来对不同的监听者发送了这一事件。
  • 下面的日志也算是一个外部通知吧,记录了这一事件。

3.3 添加boot特殊单例bean

告知完外部此时正在初始化后,接着流程对beanFactory做了一些针对的特殊处理。

ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
   beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
   ((DefaultListableBeanFactory) beanFactory)
         .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
   context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}

这里大概都可以看懂,就是注册了两个单例bean,并设置了相应的值。

  • 这里设置了单例bean包括:main方法传递下来的参数包装类,以及打印的banner。
  • 这里可能配置了两个变量:
    • AllowBeanDefinitionOverriding
    • LazyInitializationBeanFactoryPostProcessor

具体的内容我们到beanFactory的额外部分来看看这些东西具体功能是什么,暂不在流程中详细说明。

3.4 装载资源

配置完contexr在该流程中的一些特殊配置之后,此时就到了准备上下文的最后一步:

  • 装载资源。

这部分对应的代码如下:

Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);

我们一个一个方法来看。

3.4.1 获取所有资源

public Set<Object> getAllSources() {
   Set<Object> allSources = new LinkedHashSet<>();
   if (!CollectionUtils.isEmpty(this.primarySources)) {
      allSources.addAll(this.primarySources);
   }
   if (!CollectionUtils.isEmpty(this.sources)) {
      allSources.addAll(this.sources);
   }
   return Collections.unmodifiableSet(allSources);
}

这里两个点注意一下:

  • primarySources这里的一般值是我们的启动类。
  • 第二个sources这里暂时是空的,不清楚什么时候会被注入,在对应的方法里也没找到,可能是当我们自定义启动流程的时候,可以通过这个方法来做些什么?留个楔子后面看到的时候补充一下。

3.4.2 装载资源

既这部分的核心主题是装载资源,那么下面这个方法就应该是这部分的核心内容了:

load(context, sources.toArray(new Object[0]));

	protected void load(ApplicationContext context, Object[] sources) {
		if (logger.isDebugEnabled()) {
			logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
		BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
			loader.setEnvironment(this.environment);
		}
		loader.load();
	}

3.4.2.1 构建BeanDefinitionLoader

这里先创建了一个BeanDefinitionLoader,这里的步骤也很简单:

  • 先把context根据继承关系,转换成beanDefinitionRegistry对象(类图中可以看到,这个就是向上转型了)

    private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
       if (context instanceof BeanDefinitionRegistry) {
          return (BeanDefinitionRegistry) context;
       }
       if (context instanceof AbstractApplicationContext) {
          return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
       }
       throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
    }
    
  • 随后通过构造方法创建。

BeanDefinitionLoader是用来读取bean定义的,其中配置了可能需要的所有解析器以及扫描器。

BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
   Assert.notNull(registry, "Registry must not be null");
   Assert.notEmpty(sources, "Sources must not be empty");
   this.sources = sources;
   this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
   this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
   this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
   this.scanner = new ClassPathBeanDefinitionScanner(registry);
   this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

3.4.2.2 配置loader

创建完这个bean定义加载器之后,根据开关设置了loader的一些属性。

if (this.beanNameGenerator != null) {
   loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
   loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
   loader.setEnvironment(this.environment);
}

3.4.2.3 开始装载

整了这么一大堆,终于开始装载了:

loader.load();

	void load() {
		for (Object source : this.sources) {
			load(source);
		}
	}

具体是如何装载的,放到后面和BeanDefinitionLoader具体讲一讲。

总的来说,装载完之后,这些bean就会被注册到beanRegistry中。

总结

这一步进行完了之后,上下文的准备工作就结束了,按照常理来说应该就是来使用这个上下文来进行创建工作了。

这里开始,流程进入核心部分了,新的Spring核心类开始陆续出现在主流程中:

  • BeanFactory
  • BeanDefinitionLoader

接下来在额外信息部分会来做详细的解析。

源码看到这里,Spring作为一个框架,其代码的优秀在设计上就可以看出来了,比如:

  • 使用了上下文来做统一入参,这样子就可以定义一些通用的接口来做管理,符合了最小知识原则:不同的类之间所需要知道的信息越少越好。
    • 这样子的好处就在于,流程中就不需要对大部分例如listener模式下不同的监听者做很麻烦的统一规范了,各个listener各取所需即可。
  • Spring中各个组件、方法之间的职责以及分层非常清楚,命名也很简单易懂。实际上启动流程的run方法是一段事务脚本类型的代码。这种类型的代码如果在分层不明确职责划分不明确的情况下产生,那么会导致这段代码涉及到很多不该有的信息,造成阅读维护上的困难,而这个run方法很巧妙地使用了各个组件进行组合,规避了这一缺点。