Spring Boot 源码分析

在Spring Boot框架中,最先接触的应该就是SpringApplication这个类。所以源码分析也先从这个类开始。

SpringApplication用来从Java的main方法中启动Spring Boot应用。他的行为包括:

  • 创建适当的应用上下文实例
  • 对应CommandLinePropertySource和Spring的参数
  • 初始化应用上下文,并且加载单例Bean
  • 触发CommandLineRunner

其中,我们先看初始化方法。SpringApplication一共包含两个构造方法,两个构造方法均会调用initialize函数。

	private void initialize(Object[] sources) {
		if (sources != null && sources.length > 0) {
			this.sources.addAll(Arrays.asList(sources));
		}
		this.webEnvironment = deduceWebEnvironment();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

这个方法中,首先deduceWebEnvironment检验网络环境。具体方法是检查默认类加载器是否加载过Servlet和ConfigurableWebApplicationContext这两个类。如果加载过,那么即为Web应用。

第二步,getSpringFactoriesInstances方法接受ApplicationContextInitializer作为参数。然后一直调用到getSpringFactoriesInstances方法。

	private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<String>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

在getSpringFactoriesInstances方法内部,首先会调用getContextClassLoader。这里对应的类加载器为加载器AppClassLoader,负责加载java.class.path下的文件,其父加载器为ext类加载器,关系是:null(BootstrapClassLoader非java实现所以为空)->ext class loader->app class loader。至于为什使用ContextClassLoader,Spring boot中提到:

Launched applications should use Thread.getContextClassLoader() when loading classes(most libraries and frameworks will do this by default). Trying to load nested jarclasses viaClassLoader.getSystemClassLoader() will fail.

获得classloader的方法包括:

ClassLoader.getSystemClassLoader()

Thread.getContextClassLoader()

Test.class.getClassLoader()

至于使用getContextClassLoader的具体的原因,下文提到,systemClassLoader仅仅适用于简单的程序。web应用下一定会出错:

http://www.javaworld.com/article/2077344/core-java/find-a-way-out-of-the-classloader-maze.html。

要加载的类是ApplicationContextInitializer.class, 在springframework.context包下,这一类nested jar中的类,无法用systemClassLoader。因为systemClassLoader仅仅加载CLASSPATH路径下的类。无法加载nested jar中的类。如上文所写,获取classloader的一般方法为class.getClassLoader和getContextClassLoader。

之后使用ContextClassLoader加载spring.factories并找到配置文件路径,并从中找到ApplicationContextInitializer路径然后加载。之后用相同的方法加载ApplicationListener。

最后deduceMainApplicationClass方法用来确定是否是main函数启动的应用程序。确认的方法是:

	private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}
从stack中查找是否有main方法被调用。

至此,初始化完成。此后通常可以直接通过SpringApplication类中的run方法启动应用。

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.started();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			context = createAndRefreshContext(listeners, applicationArguments);
			afterRefresh(context, applicationArguments);
			listeners.finished(context, null);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			return context;
		}
		catch (Throwable ex) {
			handleRunFailure(context, listeners, ex);
			throw new IllegalStateException(ex);
		}
	}

首先创建一个StopWatch用来计时。另外configureHeadlessProperty函数用来设置java.awt.headless 属性是true 还是false, java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很多监控工具如jconsole 需要将该值设置为true.

在之前,我们加载过一批ApplicationListener,这批listener共包括四个事件:

	/**
	 * Called immediately when the run method has first started. Can be used for very
	 * early initialization.
	 */
	void started();

	/**
	 * Called once the environment has been prepared, but before the
	 * {@link ApplicationContext} has been created.
	 * @param environment the environment
	 */
	void environmentPrepared(ConfigurableEnvironment environment);

	/**
	 * Called once the {@link ApplicationContext} has been created and prepared, but
	 * before sources have been loaded.
	 * @param context the application context
	 */
	void contextPrepared(ConfigurableApplicationContext context);

	/**
	 * Called once the application context has been loaded but before it has been
	 * refreshed.
	 * @param context the application context
	 */
	void contextLoaded(ConfigurableApplicationContext context);

	/**
	 * Called immediately before the run method finishes.
	 * @param context the application context
	 * @param exception any run exception or null if run completed successfully.
	 */
	void finished(ConfigurableApplicationContext context, Throwable exception);

此时我们触发started事件。

之后是两个重要的方法:createAndRefreshContext和afterRefresh。

在createAndRefreshContext中:

	private ConfigurableApplicationContext createAndRefreshContext(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		ConfigurableApplicationContext context;
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		listeners.environmentPrepared(environment);
		if (isWebEnvironment(environment) && !this.webEnvironment) {
			environment = convertToStandardEnvironment(environment);
		}

		if (this.bannerMode != Banner.Mode.OFF) {
			printBanner(environment);
		}

		// Create, load, refresh and run the ApplicationContext
		context = createApplicationContext();
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}

		// Add boot specific singleton beans
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);

		// Load the sources
		Set<Object> sources = getSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[sources.size()]));
		listeners.contextLoaded(context);

		// Refresh the context
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
		return context;
	}
1.根据之前获得的WebEnvironment,在这里创建StandardServletEnvironment或者StandardEnvironment,前者继承后者,并重写initPropertySources方法,主要目的是初始化propertySources。此后configureEnvironment方法负责配置environment.

	protected void configureEnvironment(ConfigurableEnvironment environment,
			String[] args) {
		configurePropertySources(environment, args);
		configureProfiles(environment, args);
	}

configurePropertySources中,主要检验是否有用户自定义参数,如果有的话,并且存在commandLineRunner,那么将参数传递给commandLineRunner。

configureProfiles方法将profile保存到environment中。

全部完成后,在createAndRefreshContext方法中触发事件environmentPrepared。

到现在为止,准备工作完成。可以正式初始化应用上下文。

	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				contextClass = Class.forName(this.webEnvironment
						? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
	}

这里web应用与非web应用分别使用当前类加载器来加载AnnotationConfigEmbeddedWebApplicationContext或者AnnotationConfigApplicationContext。并使用BeanUtils.instantiate调用类加载器返回的class类的默认无参构造函数获得该上下文的实例。

在Create之后,在做一下善后工作,调用postProcessApplicationContext。

	protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
		if (this.webEnvironment) {
			if (context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;
				if (this.beanNameGenerator != null) {
					configurableContext.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());
			}
		}
	}

如果创建SpringApplication时指定ResourceLoader,这里为上下文指定ResourceLoader。resourceLoader是Spring 中用来加载资源文件的类,默认实现是DefaultResourceLoader类, web环境下还有ServletContextResourceLoader。另外为上下文添加单例bean:internalConfigurationBeanNameGenerator。这个bean用来提供命名策略服务。

在第一步时我们加载了一个ApplicationContextInitializer的列表,这时用这里的ApplicationContextInitializer来初始化上下文。

自定义ApplicationContextInitializer的作用主要是设置active profile和自定义property sources。


至此,实例化完成。开始准备进行初始化操作。此时触发contextPrepared事件。

在初始化之前,先加载sources,然后利用sources创建BeanDefinitionLoader。

	protected BeanDefinitionLoader createBeanDefinitionLoader(
			BeanDefinitionRegistry registry, Object[] sources) {
		return new BeanDefinitionLoader(registry, sources);
	}
BeanDefinitionLoader中,有XmlBeanDefinitionReader,AnnotatedBeanDefinitionReader,BeanDefinitionReader三个Reader。例如XMLBeanDefinitionReader中,有特定的注册方法,用来从XML中读取Bean信息并且统一注册至DefaultBeanDefinitionDocumentReader。

	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

在DefaultBeanDefinitionDocumentReader中,用来注册BeanDefinition的方法如下:

	protected void doRegisterBeanDefinitions(Element root) {
		// Any nested <beans> elements will cause recursion in this method. In
		// order to propagate and preserve <beans> default-* attributes correctly,
		// keep track of the current (parent) delegate, which may be null. Create
		// the new (child) delegate with a reference to the parent for fallback purposes,
		// then ultimately reset this.delegate back to its original (parent) reference.
		// this behavior emulates a stack of delegates without actually necessitating one.
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					return;
				}
			}
		}

		preProcessXml(root);
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);

		this.delegate = parent;
	}

例如我们加载一个Bean,此时会调用到方法parseBeanDefinitions内的:

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}


此时调用processBeanDefinition。在此方法中,会将ele分析并注册至getReaderContext().getRegistry()。

这里走得有点远了,我们回到load方法,此时,注册器中注册了所有的Bean的信息。此时触发事件contextLoaded。


然后主角登场。refresh方法会调用Spring中应用上下文的refresh方法,在这个方法中会完成资源文件的加载、配置文件解析、Bean定义的注册、组件的初始化等核心工作。详情可以参考Spring源码分析中的refresh方法的解释。


最后,为当前应用上下文注册shutDownHook。





版权声明:本文为jc69186918原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。