【Spring Boot實戰】源碼解析Spring Boot自動配置原理

一、簡介

Spring致力於讓Java開發更簡單,SpringBoot致力於讓使用Spring進行Java開發更簡單,SpringCloud致力於基於SpringBoot構建微服務生態圈,讓微服務開發更簡單。隨着這幾年spring官網的更新可有看出spring發展的roadmap

網上有一個spring發展的時間線,也可以看下

隨着近幾年微服務的火爆,SpringBoot及SpringCloud被使用的越來越多,瞭解其內部原理顯然越來越重要。

二、SpringBoot簡介

Spring Boot將很多魔法帶入了Spring應用程序的開發之中,其中最重要的是以下四個核心。
  自動配置:針對很多Spring應用程序常見的應用功能,Spring Boot能自動提供相關配置。
  起步依賴:告訴Spring Boot需要什麼功能,它就能引入需要的庫。
  命令行界面:這是Spring Boot的可選特性,藉此你只需寫代碼就能完成完整的應用程序,
無需傳統項目構建。
  Actuator:讓你能夠深入運行中的Spring Boot應用程序,一探究竟。

其中自動配置和起步依賴是目前和程序猿密切相關的。後面就重點分析下自動配置和起步依賴。而自動配置又是本篇的重點。

三、正題(基於springboot2.1.1分析)

1、Spring Boot的運行

以Spring Boot集成Dubbo爲例,用idea基於Spring Initialzr很方便就能搭建Spring Boot項目結構,看下項目啓動引導類

package com.jtt.hhl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DubboServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(DubboServerApplication.class, args);
    }

}

該類主要有兩個作用:配置和啓動引導。首先,這是主要的Spring配置類。雖然Spring Boot的自動配置免除了很多Spring配置,但你還需要進行少量配置來啓用自動配置。@SpringBootApplication註解開啓了Spring的組件掃描和Spring Boot的自動配置功能。自動配置就出現了。看下改註解

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;

/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @Configuration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @since 1.2.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	/**
	 * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
	 * for a type-safe alternative to String-based package names.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
	 * scan for annotated components. The package of each class specified will be scanned.
	 * <p>
	 * Consider creating a special no-op marker class or interface in each package that
	 * serves no purpose other than being referenced by this attribute.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

}

 實際上@SpringBootApplication 將三個有用的註解組合在了一起,看其註釋也很明確
  Spring的 @Configuration :標明該類使用Spring基於Java的配置。Java配置相當於spring的xml配置。
  Spring的 @ComponentScan :啓用組件掃描,這樣你寫的Web控制器類和其他組件才能被自動發現並註冊爲Spring應用程序上下文裏的Bean。
  Spring Boot 的 @EnableAutoConfiguration : 這 個 不 起 眼 的 小 注 解 也 可 以 稱 爲@Abracadabra就是這一行配置開啓了Spring Boot自動配置的魔力,讓你不用再寫成篇的配置了。

關於前兩個註解可以參考前兩遍文章【Spring實戰】----Spring配置文件的解析【Spring實戰】Spring註解配置工作原理源碼解析,本文的分析重點是註解@EnableAutoConfiguration,接下來先看引導類的啓動引導功能。

要部署Spring Boot應用程序有幾種方式,其中包含傳統的WAR文件部署。但這裏的 main() 方法讓你可以在命令行裏把該應
用程序當作一個可執行JAR文件來運行。來看下SpringApplication.run(DubboServerApplication.class, args);

/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified sources using default settings and user supplied arguments.
	 * @param primarySources the primary sources to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

最終會調用構造函數,primarySources就是DubboServerApplication.class,

/**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param resourceLoader the resource loader to use
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #setSources(Set)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

然後調用run方法

/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

run方法最重要的作用就是創建spring上下文環境,這裏根據不用的應用環境創建不同的上下文


/**
	 * The class name of application context that will be used by default for non-web
	 * environments.
	 */
	public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
			+ "annotation.AnnotationConfigApplicationContext";

	/**
	 * The class name of application context that will be used by default for web
	 * environments.
	 */
	public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
			+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

	/**
	 * The class name of application context that will be used by default for reactive web
	 * environments.
	 */
	public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
			+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";


/**
	 * Strategy method used to create the {@link ApplicationContext}. By default this
	 * method will respect any explicitly set application context or application context
	 * class before falling back to a suitable default.
	 * @return the application context (not yet refreshed)
	 * @see #setApplicationContextClass(Class)
	 */
	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

可以看出三個上下文都是基於註解的,默認的是AnnotationConfigApplicationContext,本例中classpath中沒有web、servlet的配置(改判斷就是在SpringApplication創建時初始化的),上下文環境就是AnnotationConfigApplicationContext,注意其無參構造函數

/**
     * Create a new AnnotationConfigApplicationContext that needs to be populated
     * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
     */
    public AnnotationConfigApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);   
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

在this.reader操作中,重大作用的註解處理器已經添加完畢org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)

然後準備上下文環境prepareContext(context, environment, listeners, applicationArguments, printedBanner);主要的工作就是加載引導類bean,本例中就是DubboServerApplication.class,將其註冊到beanDefinitionMap中。後面有ConfigurationClassPostProcessor處理器對其處理,解析配置類,相當於使用xml配置時xml的解析(只不過時機不同)。

再看刷新上下文環境refreshContext(context);

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

最終調用的AbstractApplicationContext中的refresh方法,是不是很熟悉了。簡單說下:

1)、prepareRefresh()刷新前的預處理,屬性設置及合法性檢驗等;

2)、obtainFreshBeanFactory();獲取BeanFactory 爲DefaultListableBeanFactory(在上下文初始化的時候已經創建);設置id(本例中是spring.application.name的值dubbo-server);

3)、prepareBeanFactory(beanFactory);BeanFactory的預準備工作(BeanFactory進行一些設置);

4)、postProcessBeanFactory(beanFactory);BeanFactory準備工作完成後進行的後置處理工作(提供給子類用的);

5)、invokeBeanFactoryPostProcessors(beanFactory);激活各種BeanFactory處理器,執行BeanFactoryPostProcessor的方法。@Configration註解的處理器ConfigurationClassPostProcessor就是在這裏調用的。

6)、registerBeanPostProcessors(beanFactory);註冊BeanPostProcessor(Bean的後置處理器)【 intercept bean creation】,這裏只是註冊,調用的時機是getBean()的時候。

7)、initMessageSource();初始化MessageSource組件(做國際化功能;消息綁定,消息解析);

8)、initApplicationEventMulticaster();初始化事件派發器;

9)、onRefresh();留給子容器(子類)

10)、registerListeners();給容器中將所有項目裏面的ApplicationListener註冊進來;

11)、finishBeanFactoryInitialization(beanFactory);初始化所有剩下的非懶加載的單實例bean(因爲有的再之前使用的時候已經初始化過了,因此這裏叫初始化剩下的非懶加載的單實例bean);

12)、finishRefresh();完成BeanFactory的初始化創建工作;IOC容器就創建完成;

至此,spring上下文,也就是IOC容器創建完成了,從最初的XmlBeanFactory、ClassPathXmlApplicationContext再到web應用中的ContextLoaderListener,再到SpringBoot的SpringApplication及@SpringBootApplication註解,其實原理都是一樣的:創建容器DefaultListableBeanFactory,註冊相關的Bean(在spring中除了容器就是bean,後置處理器就是被當成bean處理的。只不過註解的基礎bean註冊是基於@SpringBootApplication的@Configuration及@ComponentScan註解,在refresh->invokeBeanFactoryPostProcessors(beanFactory)->ConfigurationClassPostProcessor處理器中ConfigurationClassParser進行解析的,其中由於該啓動類還有@ComponentScan註解,因此還會進行組件掃描ComponentScanAnnotationParser,掃描規則爲com.jtt.hhl基礎包下的,不包含過濾規則@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class,主要是排除自動配置類及自定義配置規則;其他的config類,如本例中的ConfigTest是在掃描的時候處理的,如果是配置類還會再進行一次ConfigurationClassParser解析) 。而基於xml配置文件的是在refresh->obtainFreshBeanFactory()->loadBeanDefinitions解析xml配置文件),創建bean以及後置處理器處理(注意BeanFactoryPostProcessor和BeanPostProcessor處理時機,註解的處理都是在後置處理器中進行的),事件驅動模型。看下ConfigurationClassParser.java中的主要方法:對@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean註解的處理,這也說明了@Configuration註解類中可以使用的註解有哪些。

/**
	 * Apply processing and build a complete {@link ConfigurationClass} by reading the
	 * annotations, members and methods from the source class. This method can be called
	 * multiple times as relevant sources are discovered.
	 * @param configClass the configuration class being build
	 * @param sourceClass a source class
	 * @return the superclass, or {@code null} if none found or previously processed
	 */
	@Nullable
	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass);
		}

		// Process any @PropertySource annotations
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// Process any @ComponentScan annotations
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), true);

		// Process any @ImportResource annotations
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// Process individual @Bean methods
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

2、自動配置

上面說了註解@SpringBootApplication中的@Configuration及@ComponentScan註解,下面看一下和自動配置相關的註解@EnableAutoConfiguration,改註解的最終要的作用是導入了@Import(AutoConfigurationImportSelector.class),這個也就是自動配置開啓的地方,@Import也是向IOC容器中導入組件的一種方式,引申一下。

給容器中註冊組件;
* 1)、包掃描+組件標註註解(@Controller/@Service/@Repository/@Component)[自己寫的類]
* 2)、@Bean[導入的第三方包裏面的組件]
* 3)、@Import[快速給容器中導入一個組件]
*     1)、@Import(要導入到容器中的組件);容器中就會自動註冊這個組件,id默認是全類名
*     2)、ImportSelector:返回需要導入的組件的全類名數組;
*     3)、ImportBeanDefinitionRegistrar:手動註冊bean到容器中

@ComponentScan掃描時針對@Import註解處理,看下AutoConfigurationImportSelector的處理過程:

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);

先看一下有意思的getImports(sourceClass)方法,主要是獲取@Import註解信息

/**
	 * Returns {@code @Import} class, considering all meta-annotations.
	 */
	private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
		Set<SourceClass> imports = new LinkedHashSet<>();
		Set<SourceClass> visited = new LinkedHashSet<>();
		collectImports(sourceClass, imports, visited);
		return imports;
	}
/**
	 * Recursively collect all declared {@code @Import} values. Unlike most
	 * meta-annotations it is valid to have several {@code @Import}s declared with
	 * different values; the usual process of returning values from the first
	 * meta-annotation on a class is not sufficient.
	 * <p>For example, it is common for a {@code @Configuration} class to declare direct
	 * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
	 * annotation.
	 * @param sourceClass the class to search
	 * @param imports the imports collected so far
	 * @param visited used to track visited classes to prevent infinite recursion
	 * @throws IOException if there is any problem reading metadata from the named class
	 */
	private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {

		if (visited.add(sourceClass)) {
			for (SourceClass annotation : sourceClass.getAnnotations()) {
				String annName = annotation.getMetadata().getClassName();
				if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
					collectImports(annotation, imports, visited);
				}
			}
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}

本例中,這裏的sourceClass就是com.jtt.hhl.DubboServerApplication,最終通過將java註解(@Target、@Retention、@Documented、@Inherited)排除,一層層遞歸到EnableAutoConfiguration註解的@Import(AutoConfigurationImportSelector.class)註解及@AutoConfigurationPackage中的@Import(AutoConfigurationPackages.Registrar.class)一步步看處理:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

		if (importCandidates.isEmpty()) {
			return;
		}

		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
			this.importStack.push(configClass);
			try {
				for (SourceClass candidate : importCandidates) {
					if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();
						ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
						ParserStrategyUtils.invokeAwareMethods(
								selector, this.environment, this.resourceLoader, this.registry);
						if (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(
									configClass, (DeferredImportSelector) selector);
						}
						else {
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
							processImports(configClass, currentSourceClass, importSourceClasses, false);
						}
					}
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitions
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
						ParserStrategyUtils.invokeAwareMethods(
								registrar, this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
						// process it as an @Configuration class
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						processConfigurationClass(candidate.asConfigClass(configClass));
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}

第一個爲(class org.springframework.boot.autoconfigure.AutoConfigurationPackages$Registrar)ImportBeanDefinitionRegistrar,只是將其加入到importBeanDefinitionRegistrars中。

看第二個org.springframework.boot.autoconfigure.AutoConfigurationImportSelector爲ImportSelector,又是DeferredImportSelector類型的,因此走進this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);也只將其加入到deferredImportSelectors(延遲的導入選擇器)中備用。什麼時候用呢,繼續往下看,解析完後會調用this.deferredImportSelectorHandler.process();

public void process() {
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
					deferredImports.forEach(handler::register);
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}

這裏取出deferredImportSelectors中的AutoConfigurationImportSelector,開始處理,這裏用到了java8的新特性::及lambda表達式,重點關注下handler.processGroupImports();

public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(
							entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass),
								asSourceClasses(entry.getImportClassName()), false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}

其中的lambda表達式,重點看,先看getImports()

/**
		 * Return the imports defined by the group.
		 * @return each import with its associated configuration class
		 */
		public Iterable<Group.Entry> getImports() {
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}
	}

這裏又進行了process處理,這裏的this.group就是在::register中賦值的

public void register(DeferredImportSelectorHolder deferredImport) {
			Class<? extends Group> group = deferredImport.getImportSelector()
					.getImportGroup();
			DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
					(group != null ? group : deferredImport),
					key -> new DeferredImportSelectorGrouping(createGroup(group)));
			grouping.add(deferredImport);
			this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
					deferredImport.getConfigurationClass());
		}

就是AutoConfigurationImportSelector的內部類AutoConfigurationGroup,因此這裏的process就是調用內部類的process方法

@Override
		public void process(AnnotationMetadata annotationMetadata,
				DeferredImportSelector deferredImportSelector) {
			Assert.state(
					deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
							annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

會調用getAutoConfigurationEntry,獲取自動配置條目

/**
	 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
	 * of the importing {@link Configuration @Configuration} class.
	 * @param autoConfigurationMetadata the auto-configuration metadata
	 * @param annotationMetadata the annotation metadata of the configuration class
	 * @return the auto-configurations that should be imported
	 */
	protected AutoConfigurationEntry getAutoConfigurationEntry(
			AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

關鍵的部分:獲取所有的自動配置信息List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

/**
	 * Return the auto-configuration class names that should be considered. By default
	 * this method will load candidates using {@link SpringFactoriesLoader} with
	 * {@link #getSpringFactoriesLoaderFactoryClass()}.
	 * @param metadata the source metadata
	 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
	 * attributes}
	 * @return a list of candidate configurations
	 */
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

從哪裏獲取,classpath中的META-INF/spring.factories文件中,進入SpringFactoriesLoader類的下述方法

/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

實際上該方法在前面調用過(最一開始SpringApplication創建的時候調用setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));其中的getSpringFactoriesInstances方法就調用到了)因此cache中有值,就直接返回了key爲org.springframework.boot.autoconfigure.EnableAutoConfiguration的vule,注意這裏的map是多值map(MultiValueMap)。看下spring.factories

很多地方都有,本例中一共 size = 119個,因此可以定製,看下dubbo中spring.factoreis內容

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration


org.springframework.context.ApplicationListener=\
com.alibaba.boot.dubbo.context.event.OverrideDubboConfigApplicationListener,\
com.alibaba.boot.dubbo.context.event.WelcomeLogoApplicationListener,\
com.alibaba.boot.dubbo.context.event.AwaitingNonWebApplicationListener

因此這裏獲取的自動配置信息包含com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration,返回去看getAutoConfigurationEntry方法,會對配置信息進行過濾,去掉重複的removeDuplicates(configurations),去掉掃描排除的,重點看下filter方法

private List<String> filter(List<String> configurations,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		long startTime = System.nanoTime();
		String[] candidates = StringUtils.toStringArray(configurations);
		boolean[] skip = new boolean[candidates.length];
		boolean skipped = false;
		for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
			invokeAwareMethods(filter);
			boolean[] match = filter.match(candidates, autoConfigurationMetadata);
			for (int i = 0; i < match.length; i++) {
				if (!match[i]) {
					skip[i] = true;
					candidates[i] = null;
					skipped = true;
				}
			}
		}
		if (!skipped) {
			return configurations;
		}
		List<String> result = new ArrayList<>(candidates.length);
		for (int i = 0; i < candidates.length; i++) {
			if (!skip[i]) {
				result.add(candidates[i]);
			}
		}
		if (logger.isTraceEnabled()) {
			int numberFiltered = configurations.size() - result.size();
			logger.trace("Filtered " + numberFiltered + " auto configuration class in "
					+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
					+ " ms");
		}
		return new ArrayList<>(result);
	}

首先會根據spring.factories(spring-boot-autoconfiguration-2.1.1RELRASE.jar)中的filter進行過濾,另外過濾是還會用到另一個文件中的內容spring-autoconfigure-metadata.properties

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

這裏的過濾主要是根據spring-autoconfigure-metadata.properties文件中的內容進行過濾。簡單看下OnClassCondition過濾的過程

@Override
	protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		// Split the work and perform half in a background thread. Using a single
		// additional thread seems to offer the best performance. More threads make
		// things worse
		int split = autoConfigurationClasses.length / 2;
		OutcomesResolver firstHalfResolver = createOutcomesResolver(
				autoConfigurationClasses, 0, split, autoConfigurationMetadata);
		OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
				autoConfigurationClasses, split, autoConfigurationClasses.length,
				autoConfigurationMetadata, getBeanClassLoader());
		ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
		ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
		ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
		System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
		System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
		return outcomes;
	}

這裏將配置類一分爲二用了多線程提高效率,看下怎麼過濾的,就是判斷classpath中有沒有該類

private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
				int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
			ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
			for (int i = start; i < end; i++) {
				String autoConfigurationClass = autoConfigurationClasses[i];
				if (autoConfigurationClass != null) {
                    //從properties中獲取對應的類信息
					String candidates = autoConfigurationMetadata
							.get(autoConfigurationClass, "ConditionalOnClass");
					if (candidates != null) {
						outcomes[i - start] = getOutcome(candidates);  
                        //去classpath中看看有沒有,沒有則match爲false,並顯示對應的信息
					}
				}
			}
			return outcomes;
		}
private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
			if (ClassNameFilter.MISSING.matches(className, classLoader)) {
				return ConditionOutcome.noMatch(ConditionMessage
						.forCondition(ConditionalOnClass.class)
						.didNotFind("required class").items(Style.QUOTE, className));
			}
			return null;
		}


MISSING {

			@Override
			public boolean matches(String className, ClassLoader classLoader) {
				return !isPresent(className, classLoader);
			}

		};

public static boolean isPresent(String className, ClassLoader classLoader) {
			if (classLoader == null) {
				classLoader = ClassUtils.getDefaultClassLoader();
			}
			try {
				forName(className, classLoader);
				return true;
			}
			catch (Throwable ex) {
				return false;
			}
		}

		private static Class<?> forName(String className, ClassLoader classLoader)
				throws ClassNotFoundException {
			if (classLoader != null) {
				return classLoader.loadClass(className);
			}
			return Class.forName(className);
		}

其實最終就是基於Class.forName(className);進行判斷的,這也是condition使用的底層支持。

值得注意的是本例中的DubboAutoConfiguration並沒在其中,所以對應的outcomes爲null,match返回的是ture,經過OnClassCondition、OnWebApplicationCondition及OnBeanCondition的過濾後最終配置類還剩10個配置類(包含DubboAutoConfiguration)條目。

至此,condition出現了,上面的importfilter分別對應註解@ConditionalOnBean、@ConditionalOnClass、@ConditionalOnWebApplication含義分別是僅僅在當前上下文中存在某個對象時,纔會啓用;某個class位於類路徑上,纔會啓用,該註解的參數對應的類必須存在,否則不解析該註解修飾的配置類;在web應用下才會啓用該配置類,看下dubbo的自動配置類

@Configuration
@ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true, havingValue = "true")
@ConditionalOnClass(AbstractConfig.class)
public class DubboAutoConfiguration {

還用到了@ConditionalOnProperty註解,配置屬性dubbo.enabled的值存在或者缺失都會啓用該配置,另外還必須存在AbstractConfig類。

回過頭去繼續看lambda表達式:

grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(
							entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass),
								asSourceClasses(entry.getImportClassName()), false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});

針對上述過濾後剩餘的10個配置類條目進行處理,繼續調用

processImports(configurationClass, asSourceClass(configurationClass), asSourceClasses(entry.getImportClassName()), false);

這次上述兩個條件都不滿足,走到else當成配置類繼續解析

else {
    // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
    // process it as an @Configuration class
    this.importStack.registerImport(
        currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
    processConfigurationClass(candidate.asConfigClass(configClass));
}

而其中的@EnableDubboConfig註解導入了@Import(DubboConfigConfigurationSelector.class)

/**
     * Single Dubbo Config Configuration
     *
     * @see EnableDubboConfig
     * @see DubboConfigConfiguration.Single
     */
    @EnableDubboConfig
    protected static class SingleDubboConfigConfiguration {
    }

因此會繼續處理DubboConfigConfigurationSelector,這裏採用的是遞歸處理。所有都處理完後得到ConfigClasses,繼續處理配置類中的Bean,也就是用@Bean註解的bean信息,將其註冊到IOC容器中。

// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);
/**
	 * Determine if an item should be skipped based on {@code @Conditional} annotations.
	 * @param metadata the meta data
	 * @param phase the phase of the call
	 * @return if the item should be skipped
	 */
	public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}

		if (phase == null) {
			if (metadata instanceof AnnotationMetadata &&
					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
			}
			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
		}

		List<Condition> conditions = new ArrayList<>();
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}

		AnnotationAwareOrderComparator.sort(conditions);

		for (Condition condition : conditions) {
			ConfigurationPhase requiredPhase = null;
			if (condition instanceof ConfigurationCondition) {
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
	}

 

至此配置類就解析完畢了,總結下:

1、首先對springboot配置類(com.jtt.hhl.DubboServerApplication)利用配置後置處理器ConfigurationClassPostProcessor進行解析,解析過程中利用其上的ComponentScan註解對其他組件進行掃描註冊,包含(自定義配置類com.jtt.hhl.config.ConfigTest,由於@Component是@Configuration的元註解);利用其上的@EnableAutoConfiguration註解進行自動配置發現,主要是利用@Import導入AutoConfigurationImportSelector註解(解析時根據@Import註解導入的類型不同處理不同),讀取META-INF/spring.factories中的配置類;還會對配置類上的@PropertySource註解、@ImportResource註解以及配置類內的@Bean註解進行處理。

2、自動配置的啓用:根據spring.factories中的filter和spring-autoconfigure-metadata.properties中對應filter信息進行過濾,實際上就是自動配置的過程,如果classpath中有相應的配置就啓用,繼續進行配置類的解析操作,循環往復,直到所有啓用的配置類都解析完畢,這個過程和使用xml配置時解析的過程類似。

3、@Conditional註解的處理,在配置類解析時會首先對配置類進行ConditionEvaluator處理(其實每個配置類都會進行處理,而且在loadBeanDefinitions(configClasses)配置類中的bean definition是也會進行@Conditional的處理(如果有的話)),判斷條件是否滿足,如果滿足再繼續進行配置類的解析(上面說com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration.OnClassCondition沒在文件中,雖然當時沒有被過濾,這裏仍然會判斷其上的註解條件),並將其中滿足條件的bean註冊到IOC容器中。因此在配置類com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration上的@Conditional註解在配置類解析時會進行處理。

至此,自動配置的全過程也就完畢了,自動配置的過程也就是SpringBoot啓動類(也是配置類)的配置解析過程,配置類解析過程中會進行自動配置,自動配置就是讀取META-INF/spring.factories中的配置類,然後利用@Conditional相關的註解,在classpath中判斷對應的class是否存在(@ConditionalOnClass)、在propeties文件中判斷對應的屬性是否存在(@ConditionalOnProperty)、在IOC容器判斷對應的bean是否存在(@ConditionalOnBean)而是否啓用對應的配置,也是利用條件判斷當自動配置不滿足條件時可以實現自定義配置。

最後,沒有實例化非懶加載單實例bean會在finishBeanFactoryInitialization(beanFactory);中進行實例化。

四、代碼地址

https://github.com/honghailiang/springboot-dubbo

 

五、微信公衆號

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章