Skip to content

Latest commit

 

History

History
1060 lines (889 loc) · 42.5 KB

README.md

File metadata and controls

1060 lines (889 loc) · 42.5 KB

Spring Boot 自动配置(autoconfigure)原理

0. 说明

环境配置清单

java version "1.8.0_161"

Java(TM) SE Runtime Environment (build 1.8.0_161-b12)

Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)

Spring Boot 2.1.0.RELEASE

项目 GitHub

1. 前提知识

一、SPI扩展机制

1. 解释

SPI: Service Provider Interface , 即 服务提供接口

2. 如何写一个Java SPI呢?

  1. 定义一组接口, 接口是 com.glmapper.spi.FilterProvider
  2. 接口的一个或多个实现(com.glmapper.spi.provider.FileFilterProvider [从文件系统加载filter], com.glmapper.spi.provider.DataSourceFilterProvider [从数据源中加载filter]);
  3. src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 com.glmapper.spi.FilterProvider, 内容是要对应的实现类(com.glmapper.spi.provider.FileFilterProvidercom.glmapper.spi.provider.DataSourceFilterProvider 或两者);
  4. 使用 ServiceLoader 来加载配置文件中指定的实现。

3. SPI应用案例

  1. Dubbo 中有大量的SPI应用,不过Dubbo不是原生的java spi机制,他是原生的一个变种 . Dubbo SPI 约定:

    1. 扩展点约定 : 扩展点必须是 Interface 类型 , 必须被 @SPI 注解 , 满足这两点才是一个扩展点。
    2. 扩展定义约定 : 在 META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal/目录下新建扩展点文件,这些路径下定义的文件名称为扩展点接口的全类名 , 文件中以键值对的方式配置扩展点的扩展实现。例如文件 META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory 中定义的扩展 :
    adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
    spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
    spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory

    关于Dubbo SPI扩展机制在此不再继续展开描述

  2. JDBC 数据库驱动包: java mysql 驱动采用原生的spi机制mysql-connector-java-xxx.jar 就有一个 /META-INF/services/java.sql.Driver 里面内容是

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
  1. 当然还有今天的主角 spring boot ,他也是原生spi的变种,它的约定是在src/main/resorces/下建立META-INF/spring.factories, 当springboot服务启动时,对象实例化过程会加载META-INF/spring.factories文件,将该配置文件中的配置的类载入到Spring容器中.下面是spring-boot-autoconfigure jar包中spring.factories 的内容:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

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

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
# 这里省略了一堆

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.JspTemplateAvailabilityProvider

2. Spring Boot 自动配置机制

0. 总体流程概述

1. 几个重要的事件回调机制

ApplicationContextInitializer

配置在META-INF/spring.factories

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

ApplicationContextInitializer 是上下文初始化入口

image-20181125164451526

SpringApplicationRunListener

配置在META-INF/spring.factories

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

SpringApplicationRunListener 的功能是监听容器启动过程也就是SpringApplication.run()方法,

image-20181125164340483

ApplicationRunner & CommandLineRunner

CommandLineRunner & ApplicationRunner 接口是在容器启动成功后的最后一步回调(类似开机自启动), 两者功能差不多, 只需要将其实现类放在IOC容器中,应用启动后会自动回调接口方法

image-20181125164546033

image-20181125164642736

2. 自动配置注解

@EnableAutoConfiguration

@EnableAutoConfiguration是自动配置的开关, 下面看看他的结构

/**
 * Enable auto-configuration of the Spring Application Context, attempting to guess and
 * configure beans that you are likely to need. Auto-configuration classes are usually
 * applied based on your classpath and what beans you have defined. For example, if you
 * have {@code tomcat-embedded.jar} on your classpath you are likely to want a
 * {@link TomcatServletWebServerFactory} (unless you have defined your own
 * {@link ServletWebServerFactory} bean).
 * <p>
 * When using {@link SpringBootApplication}, the auto-configuration of the context is
 * automatically enabled and adding this annotation has therefore no additional effect.
 * <p>
 * Auto-configuration tries to be as intelligent as possible and will back-away as you
 * define more of your own configuration. You can always manually {@link #exclude()} any
 * configuration that you never want to apply (use {@link #excludeName()} if you don't
 * have access to them). You can also exclude them via the
 * {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied
 * after user-defined beans have been registered.
 * <p>
 * The package of the class that is annotated with {@code @EnableAutoConfiguration},
 * usually via {@code @SpringBootApplication}, has specific significance and is often used
 * as a 'default'. For example, it will be used when scanning for {@code @Entity} classes.
 * It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're
 * not using {@code @SpringBootApplication}) in a root package so that all sub-packages
 * and classes can be searched.
 * <p>
 * Auto-configuration classes are regular Spring {@link Configuration} beans. They are
 * located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
 * Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
 * often using {@link ConditionalOnClass @ConditionalOnClass} and
 * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @see ConditionalOnBean
 * @see ConditionalOnMissingBean
 * @see ConditionalOnClass
 * @see AutoConfigureAfter
 * @see SpringBootApplication
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包,注册扫描包
@AutoConfigurationPackage
// 导入的这个AutoConfigurationImportSelector是自动配置的关键
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	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
	 */
	String[] excludeName() default {};

}

进入AutoConfigurationImportSelector找到selectImports()方法,他调用了getCandidateConfigurations()方法,在这里,这个方法又调用了Spring Core包中的loadFactoryNames()方法。这个方法的作用是,会查询META-INF/spring.factories文件中包含的JAR文件。

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        //1. 得到注解信息
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
            autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

	/**
	 * 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;
		}
        // 2. 得到注解中的所有属性信息
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 3. 得到spring.factories中配置在EnableAutoConfiguration下的字符串列表
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
        // 4. 去重
		configurations = removeDuplicates(configurations);
        // 5. 根据注解中的exclude信息去除不需要的
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
        // 7. 派发事件
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

	/**
	 * 获取所有的自动配置类,也就是配置在spring.factories中 EnableAutoConfiguration 下的所有字符串列表
	 * 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) {
        // getSpringFactoriesLoaderFactoryClass()直接返回EnableAutoConfiguration.class
        // 所以这一步加载了所有的自动配置类
		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;
	}

	/**
	 * Return the class used by {@link SpringFactoriesLoader} to load configuration
	 * candidates.
	 * @return the factory class
	 */
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

下面进入org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames

/**
 * Load the fully qualified class names of factory implementations of the
 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
 * class loader.
 * @param factoryClass the interface or abstract class representing the factory
 * @param classLoader the ClassLoader to use for loading resources; can be
 * {@code null} to use the default
 * @throws IllegalArgumentException if an error occurs while loading factory names
 * @see #loadFactories
 */
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    // 获取全类名
   String factoryClassName = factoryClass.getName();
    // 加载所有的spring.factories中的配置,然后筛选出factoryClassName下的配置的值
   return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

image-20181125170438713

在上面的spring-boot-autoconfigure.jar里的spring.factories文件下我们可以看到有这么一段关于EnableAutoConfiguration的配置(放一小段)

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

SpringBoot启动配置类上面打上@EnableAutoConfiguration注解之后springboot就会实例化配置文件中这些XxxAutoConfiguration类启用这些类的功能,

==需要注意的是: 加了@EableAutoConfiguration注解的配置类只会为这个类所在的包以及子包下面的类自动配置==

@EnableAutoConfiguration是自动配置的开关 ,如果要自己写自动配置类,还有一些Conditional的注解类需要掌握

@ConditionalOnXxx系列注解

SpringBoot的自动配置全都依赖于这个系列的注解,下面列举了一些:

ConditionalOnBean 当指定bean存在时, 配置生效 ConditionalOnClass 当指定类存在时, 配置生效 ConditionalOnCloudPlatform 当项目环境为指定云平台环境时, 配置生效 ConditionalOnEnableResourceChainResourceChain是启用状态时, 配置生效 ConditionalOnExpression 当表达式为true时, 配置生效 ConditionalOnJava 当环境的java为指定版本时,配置生效 ConditionalOnJndi 当指定的JNDI存在时, 配置生效 ConditionalOnMissingBean 当指定的bean不存在时, 配置生效 ConditionalOnMissingClass 当指定的类不存在时, 配置生效 ConditionalOnNotWebApplication 当项目为非web项目时, 配置生效 ConditionalOnProperty 当指定的配置存在时, 配置生效 ConditionalOnResource 当指定的资源存在时, 配置生效 ConditionalOnSingleCandidate 当指定的类是单例时, 配置生效 ConditionalOnWebApplication 当项目是web项目时, 配置生效

image-20181125172244614

举个栗子

下面以HttpEncodingAutoConfiguration为例来看一下自动配置

@ConditionalOnProperty注解的玩法很多, 详细使用案例参考本文文末附件**@ConditionalOnProperty注解**

/**
 * {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
 * in web applications.
 *
 * @author Stephane Nicoll
 * @author Brian Clozel
 * @since 1.2.0
 */
@Configuration
// 启用HttpProperties配置并加入到IOC容器
@EnableConfigurationProperties(HttpProperties.class)
// 当项目是servlet容器下的web项目时,这个配置类才生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
// 当CharacterEncodingFilter类存在时,这个配置类才生效
@ConditionalOnClass(CharacterEncodingFilter.class)
// 当spring.http.encoding.enabled这个环境变量存在且值不为false时,这个配置类才生效
// @ConditionalOnProperty这个注解的玩法很多
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

   private final HttpProperties.Encoding properties;

   public HttpEncodingAutoConfiguration(HttpProperties properties) {
      this.properties = properties.getEncoding();
   }

   @Bean
   // 当容器中没有CharacterEncodingFilter类型的实例时,这个方法生效
   @ConditionalOnMissingBean
   public CharacterEncodingFilter characterEncodingFilter() {
      CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
      filter.setEncoding(this.properties.getCharset().name());
      filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
      filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
      return filter;
   }

   @Bean
   public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
      return new LocaleCharsetMappingsCustomizer(this.properties);
   }

   private static class LocaleCharsetMappingsCustomizer implements
         WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {

      private final HttpProperties.Encoding properties;

      LocaleCharsetMappingsCustomizer(HttpProperties.Encoding properties) {
         this.properties = properties;
      }

      @Override
      public void customize(ConfigurableServletWebServerFactory factory) {
         if (this.properties.getMapping() != null) {
            factory.setLocaleCharsetMappings(this.properties.getMapping());
         }
      }

      @Override
      public int getOrder() {
         return 0;
      }
   }
}

3. SpringBoot启动过程

0. 总体流程概述

1. 创建启动类

1.1. 创建启动类

@SpringBootApplication
public class Bootstrap {
    public static void main(String[] args) {
        // 调用SpringApplication静态方法run为入口
        SpringApplication.run(Bootstrap.class, args);
    }
}

1.2. 跟踪进入org.springframework.boot.SpringApplication#run(java.lang.String...)

    /**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified sources using default settings and user supplied arguments.
     * @param sources the sources to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        // 进入构造器,会有初始化过程
       return new SpringApplication(sources).run(args);
    }
	/**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param sources the bean sources
	 * @see #run(Object, String[])
	 * @see #SpringApplication(ResourceLoader, Object...)
	 */
	public SpringApplication(Object... sources) {
        // 初始化
		initialize(sources);
	}
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void initialize(Object[] sources) {
		if (sources != null && sources.length > 0) {
			this.sources.addAll(Arrays.asList(sources));
		}
        // 推断是否为web环境
		this.webEnvironment = deduceWebEnvironment();
        // 获取所有的配置在spring.factores中的ApplicationContextInitializer
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
        // 获取所有的配置在spring.factores中的ApplicationContextInitializer
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 推断启动类,我们这里就是Bootstrap.class
		this.mainApplicationClass = deduceMainApplicationClass();
	}
	/**
	 * 运行spring应用,创建一个新的spring上ApplicationContext下文环境
	 * 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;
		FailureAnalyzers analyzers = null;
        // 加载java的AWT图形化相关的系统配置变量, 可以忽略
		configureHeadlessProperty();
        // 实例化spring.factories中配置的所有SpringApplicationRunListener并返回到 listeners 中
        // SpringApplicationRunListeners内部是一个List<SpringApplicationRunListener>
		SpringApplicationRunListeners listeners = getRunListeners(args);
        // 启动应用监听器,回调starting方法,
        // spring应用进行到某一阶段时会广播通知所有的监听器, 监听器的方法就会被回调执行
		listeners.starting();
		try {
            // 包装命令行启动参数 也就是 Bootstrap.main(String[] args)中的args
            // 我们可以通过命令号启动应用 java -jar demo.jar --server.port=8989 这个server.port=8989就是启动参数
            // 他可以接受多个启动参数,包括指定profile [dev/test/pre/prod]
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备应用环境, 包括读取系统环境变量/yml,properties等配置文件, 
            // 同时回调listeners的environmentPrepared方法
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
            // 打印Banner 也就是我们启动应用时控制台打印出的Spring 的 logo了,这个也可以自定义
            // 有兴趣的自行百度自定义springboot banner, 我不喜欢这些花里胡哨的东西(才怪)
			Banner printedBanner = printBanner(environment);
            // 创建上下文,决定创建web的ioc还是普通的ioc
			context = createApplicationContext();
            // 实例化配置在spring.factories中的FailureAnalyzer应用启动失败的分析器,并返回
			analyzers = new FailureAnalyzers(context);
            // 上下文准备,会广播通知listeners回调contextPrepared方法
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
            // 刷新上下文
			refreshContext(context);
            // 上下文刷新后的一些擦屁股工作
			afterRefresh(context, applicationArguments);
            // 容器已经创建和刷新完成,广播通知listeners回调finished方法
			listeners.finished(context, null);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
            // 到此如果没有启动报错,那你的应用就已经启动完成了
			return context;
		}
		catch (Throwable ex) {
			handleRunFailure(context, listeners, analyzers, ex);
			throw new IllegalStateException(ex);
		}
	}

我们要说的是springboot自动配置, 但是我写这些做什么呢? 因为自动配置就是在上面的一些步骤中完成的,下面继续

总结一下,应用启动过程经历了哪些阶段呢.

  1. getRunListeners(...)获取SpringApplicationRunListener监听器
  2. prepareEnvironment(...)应用环境准备
  3. createApplicationContext(...)创建应用上下文
  4. prepareContext(...)上下文准备
  5. refreshContext(...)刷新上下文
  6. afterRefresh(...)上下文刷新完后的一些收尾工作

2. prepareEnvironment

容器环境准备阶段

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                   ApplicationArguments applicationArguments) {
    // 存在就获取环境,不存在就创建环境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 环境配置:
    // 1. 收集用户自定义的配置和系统环境变量
    // 2. 收集Profiles信息
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 遍历listeners 调用 environmentPrepared
    listeners.environmentPrepared(environment);
    // 把环境绑定到SpringApplication, 实际上是增加了一个K-V键值对==>
    // "spring.main" = SpringApplication的Bindable对象
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        // 推断项目的环境,并把当前环境转换成项目所需要的环境
        environment = new EnvironmentConverter(getClassLoader())
            .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

3. createApplicationContext

根据是否为web环境来决定创建一个web应用或者非web应用的上下文

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            // 根据环境来创建对应的上下文, 下面的值的包名我省略了
            //DEFAULT_CONTEXT_CLASS: "AnnotationConfigApplicationContext";
            //DEFAULT_SERVLET_WEB_CONTEXT_CLASS: "AnnotationConfigServletWebServerApplicationContext";
            //DEFAULT_REACTIVE_WEB_CONTEXT_CLASS: "AnnotationConfigReactiveWebServerApplicationContext";
            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);
}

4. prepareContext

上下文的一些成员变量初始化工作

private void prepareContext(ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
    // 做了三件事:
    // 1. 注册beanNameGenerator到Context中
    // 2. 为Context设置资源加载器resourceLoader
    // 3. 为Context设置类加载器
    // 4. 为Context设置ConversionService, ConversionService是提供值转换服务的
   postProcessApplicationContext(context);
    // 触发ApplicationContextInitializer初始化方法,初始化上下文
   applyInitializers(context);
    // 遍历触发listener的contextPrepared方法
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }

   // Add boot specific singleton beans  把命令行参数添加到ioc中
   context.getBeanFactory().registerSingleton("springApplicationArguments",
         applicationArguments);
   if (printedBanner != null) {
      context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
       ((DefaultListableBeanFactory) beanFactory)
        .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   // Load the sources
   Set<Object> sources = getSources();
   Assert.notEmpty(sources, "Sources must not be empty");
    // 加载上下文:
    // 1. 实例化 BeanDefinitionLoader
    // 2. 执行load()方法
    //  2.1 加载启动类上的注解、解析注解元信息、
   load(context, sources.toArray(new Object[sources.size()]));
    // 遍历listener调动contextLoaded上下文加载完成方法
   listeners.contextLoaded(context);
}

// load()最终会到AnnotatedBeanDefinitionReader#doRegisterBean方法,看看做了些啥
<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {

    // 分析启动类的注解的信息
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
        return;
    }
    // Supplier无参数有返回值的接口方法
    abd.setInstanceSupplier(instanceSupplier);
    //检查scope,实例中没有指定,默认是singleton
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
    abd.setScope(scopeMetadata.getScopeName());
    ////获取bean的名字,这里是启动类
    String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
    // 对于是否是@lazy,是否使用了@primary
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    if (qualifiers != null) {
        for (Class<? extends Annotation> qualifier : qualifiers) {
            if (Primary.class == qualifier) {
                abd.setPrimary(true);
            }
            else if (Lazy.class == qualifier) {
                abd.setLazyInit(true);
            }
            else {
                abd.addQualifier(new AutowireCandidateQualifier(qualifier));
            }
        }
    }
    for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
        customizer.customize(abd);
    }
    // 根据注解信息生产BeanDefinition
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    // 根据Bean的作用域,创建相应的代理对象
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    // 将Bean加入到beanDefinitionMap中
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

5. refreshContext

最终会定位到org.springframework.context.support.AbstractApplicationContext#refresh方法, 除此之外最后还会注册ShutdownHook

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      // 1. 清除缓存
      // 2. 初始化所有在上下文环境中的占位符配置
      // 3. 校验所有required的配置是否已经被解析完成
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      // 通知子类刷新Bean工厂
      //  1. 为BeanFactory设置了一个ID, 就是在yaml文件中配置的spring.application.name
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      // 1. 为BeanFactory设置类加载器
      // 2. 设置表达式解析器StandardBeanExpressionResolver
      // 3. 设置配置文件注册器ResourceEditorRegistrar
      // 4. 设置Bean的后置处理器
      // 不一一列举了,下面看图
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         // 1. 添加子类自定义的Bean后置处理器
         // 2. 扫描basePackage下的类,按照需求加入到容器
         // 3. 把带有Spring注解的类加入到容器
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         // 调用IOC容器中所有的Bean工厂处理器 BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor
         // 1. 配置类后置处理器 ConfigurationClassPostProcessor 解析 配置类 转换为 BeanDefinition
         //    1.1 @ComponentScan注解配置的basePackage
         //    1.2 @Import注解导入的配置类
         //    1.3 @ImportResource注解导入的xml文件
         //    1.4 @Bean注解的方法
         //    1.5 @PropertySource注解导入的.properties配置文件
         //    1.6 处理所有的SpringBoot配置类
         // 2. 后置处理器太多了, 功能列不过来了
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         // 注册所有 用于拦截Bean创建的BeanProcessor
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         // 初始化MessageSource, 供i18n用的
         initMessageSource();

         // Initialize event multicaster for this context.
         // 初始化事件广播器, 在这之前的listener都是遍历直接调用的方法, 从这里开始,listener会通过接受广播的方式回调
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         // 初始化其他特定的Bean在指定的容器中,比如父子容器
         // 比如在web容器中会初始化TomcatWebServer
         onRefresh();

         // Check for listener beans and register them.
         // 检查并注册listener到广播器
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         // 初始化所有的非懒加载的单例对象
         // 需要注意的是在AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])
         // 这个方法中有一段如下代码
         // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
         // 这个方法注释的意思是给BeanPostProcessor一个返回目标接口的代理对象的机会, 具体可查阅和
         // InstantiationAwareBeanPostProcessor相关的资料
	     // Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         // 完成刷新: 
         // 1. 清除各种缓存
         // 2. 初始化生命周期处理器
         // 3. 发布ContextRefreshedEvent事件
         // 4. 启动WebServer
         // 5. 发布ServletWebServerInitializedEvent时间
         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();
      }
   }
}

prepareBeanFactory

image-20181126190231737

6. afterRefresh

这个阶段SpringBoot没有具体的实现,留给开发者自定义子类去实现

/**
 * Called after the context has been refreshed.
 * @param context the application context
 * @param args the application arguments
 */
protected void afterRefresh(ConfigurableApplicationContext context,
      ApplicationArguments args) {
}

4. 附件

@ConditionalOnProperty注解
一、@ConditionalOnProperty 结构
@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.TYPE, ElementType.METHOD})  
@Documented  
@Conditional({OnPropertyCondition.class})  
public @interface ConditionalOnProperty {  
    //数组,获取对应property名称的值,不可与name同时使用  
    String[] value() default {};
  
    //property名称的前缀,可有可无
    String prefix() default "";  
  
    //数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),不可与value同时使用 
    String[] name() default {}; 
  
    //可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置  
    String havingValue() default "";
  
    //缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错
    boolean matchIfMissing() default false;  
  
    //是否可以松散匹配  
    boolean relaxedNames() default true;
} 
二、@ConditionalOnProperty 用法

1. 有如下spring boot代码和yml配置

@Configuration  
@ConditionalOnProperty(value = "object.pool.size")  
public class ObjectPoolConfig { 

}

yml配置如下:

object.pool:  
    size: true     //正常  
object.pool:  
    size:          //正常,空字符时   
object.pool:  
    size: false    //失败  
object.pool:  
    size: null     //正常  
object.pool:  
    size: 30       //正常 

2. 有如下spring boot代码和yml配置

@Configuration  
@ConditionalOnProperty(value = "object.pool.size",havingValue="30")  
public class ObjectPoolConfig { 

}

yml配置如下:

object.pool:  
    size: 1234     //失败,与havingValue给定的值不一致     
object.pool:  
    size: false    //失败,与havingValue给定的值不一致  
object.pool:  
    size: 30       //正常 

当且仅当配置文件中的Value和havingValue的值一致时才加载成功 3. 有如下spring boot代码和yml配置

@Configuration  
@ConditionalOnProperty(prefix = "object.pool",name = "size",havingValue="30")  
public class ObjectPoolConfig { 

}

yml配置如下:

object.pool:  
    size: 1234     //失败,与havingValue给定的值不一致
object.pool:  
    size: false    //失败,与havingValue给定的值不一致
object.pool:  
    size: 30       //正常 

4. 有如下spring boot代码和yml配置

@Configuration  
@ConditionalOnProperty(prefix = "object.pool",name = "size",havingValue="30",matchIfMissing = true)  
public class ObjectPoolConfig { 

}

yml不配置相关参数,正常启动,当 matchIfMissing = true 时,即使没有 object.pool.size 属性也会加载正常 5. 有如下spring boot代码和yml配置

@Configuration  
//matchIfMissing的缺省值为false
@ConditionalOnProperty(prefix = "object.pool",name = "size",havingValue="30",matchIfMissing = false)  
public class ObjectPoolConfig { 

}

yml配置如下:

yml不配置相关参数,加载失败,当 matchIfMissing = false 时,必须要有对应的属性配置

object.pool:  
    size: 1234     //失败,与havingValue给定的值不一致
object.pool:  
    size: false    //失败,与havingValue给定的值不一致
object.pool:  
    size: 30       //正常 

6. 有如下spring boot代码和yml配置

@Configuration  
@ConditionalOnProperty(prefix = "object.pool",name = {"size","timeout"})  //name中的属性需要两个都存在且都不为false才会加载正常  
public class ObjectPoolConfig { 

}

yml配置如下:

object.pool: 
    timeout: true
    size: 1234     //正常
object.pool:  
    timeout: true
    size: false    //失败,两个值都不能为 false
object.pool:  
    timeout: true
    size: true     //正常 

7. 有如下spring boot代码和yml配置

@Configuration  
@ConditionalOnProperty(prefix = "object.pool",name = {"size","timeout"},havingValue="false") 
public class ObjectPoolConfig { 

}

.yml配置如下:

object.pool:  
    timeout: false
    size: false    //正常

8. 有如下spring boot代码和yml配置

@Configuration  
@ConditionalOnProperty(prefix = "object.pool", name = {"size", "timeout"}, havingValue = "123", matchIfMissing = true) 
public class ObjectPoolConfig { 

}

yml配置如下:

object.pool:  
    timeout: 123
    size: false     //失败,和havingValue的值不一致
object.pool:  
    timeout: 123
    size: 1234      //失败,和havingValue的值不一致
object.pool:  
    timeout: 123
    size: 123       //正常

matchIfMissing = true , 不配置参数也正常

三、 @ConditionalOnProperty 应用场景
  1. 通过 @ConditionalOnProperty 来控制 Configuration 是否生效