spring boot原理_spring-boot运行原理

自动配置、起步依赖、Actuator、命令行界面(CLI)是Spring Boot最重要的4大核心特性。

spring-boot使用“习惯优于配置”(项目中存在大量的配置,此外还内置一个习惯性的配置,让你无须手动进行配置)的理念让你的项目快速运行起来。关于自动配置的源码在spring-boot-autoconfigure-xxx.jar中。

909baac8b163ecd98fb9a0324874040b.png

一、查看项目中的自动配置报告

可通过下面方式查看当前项目中已启用(Positive matches)和未启用(Negative matches)的自动配置的报告。

1)运行jar 时增加--debug 参数,如java -jar demo_work-20181024-SNAPSHOT.jar --debug

2)在application.yml中设置属性(debug: true)

3)运行时设置jvm启动参数,如java -Ddebug -jar demo_work-20181024-SNAPSHOT.jar

常见错误:mvn打包时可能会报No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?错误。

可人为指定javac.exe的地址,示例如下:

<plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
   <plugin>
             <artifactId>maven-compiler-plugin</artifactId>
             <configuration>
                     <fork>true</fork>
                        <executable>
                        C:Program FilesJavajdk1.8.0_73binjavac.exe
                       </executable>
             </configuration>
         </plugin>
  </plugins>

二、Spring Boot的运行原理

2.1 @EnableAutoConfiguration

它的核心功能自动配置是由@EnableAutoConfiguration 注解提供的

@EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
 
    Class<?>[] exclude() default {};
 
    String[] excludeName() default {};
}

@AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
 
}

@Import 注解导入的配置功能, EnableAutoConfigurationlmportSelector 使用SpringFactoriesLoader.loadFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包。而spring. factories 文件存在于spring-boot-autoconfigure- xxx.jar中。

b7480d8b4469a19594ec08f880a3a67a.png

加载自动配置的方法

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
   String factoryClassName = factoryClass.getName();
   try {
      // 扫描META-INF/spring.factories文件
      Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      List<String> result = new ArrayList<String>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
         String factoryClassNames = properties.getProperty(factoryClassName);
         result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
      }
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
            "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
 

补充:SpringBoot 的 @Import 用于将指定的类实例注入之Spring IOC Container中。 EnableAutoConfigurationlmportSelector 继承了AutoConfigurationImportSelector,间接实现 ImportSelector 注入Bean。

2.2 spring.factories文件

53b9cfd2dbf2eef0c6382d09ad7621f5.png

打开上面任意一个AutoConfiguration 文件,一般都有下面的条件注解,在spring-boot-autoconfigure-xxx.jar 的org.springframwork.boot.autoconfigure.condition 包下,条件注解如下。

@ConditionalOnBean :当容器里有指定的Bean 的条件下。

@ConditionalOnClass :当类路径下有指定的类的条件下。

@ConditionalOnCloudPlatform:当指定了云平台的时候

@ConditionalOnExpression :基于SpEL 表达式作为判断条件

@ConditionalOnJava:基于JVM 版本作为判断条件。

@ConditionalOnJndi :在JNDI 存在的条件下查找指定的位置。

@ConditionalOnMissingBean :当容器里没有指定Bean 的情况下。

@ConditionalOnMissingClass :当类路径下没有指定的类的条件下。

@ConditionalOnNotWebApplication :当前项目不是Web 项目的条件下。

@ConditionalOnProperty :指定的属性是否有指定的值。

@ConditionalOnResource :类路径是否有指定的值。

@ConditionalOnSingleCandidate:当指定Bean 在容器中只有一个,或者虽然有多个但是指定首选的Bean

@Conditional On WebApplication:当前项目是Web 项目的条件下。

这些注解都是组合了@Conditional 元注解,只是使用了不同的条件( Condition ),下面我分析一下@ConditionalOnWebApplication 注解。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnWebApplicationCondition.class})
public @interface ConditionalOnWebApplication {
}
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends SpringBootCondition {
 
   private static final String WEB_CONTEXT_CLASS = "org.springframework.web.context."
 + "support.GenericWebApplicationContext";
 
   @Override
 public ConditionOutcome getMatchOutcome(ConditionContext context,
         AnnotatedTypeMetadata metadata) {
      boolean required = metadata
            .isAnnotated(ConditionalOnWebApplication.class.getName());
      ConditionOutcome outcome = isWebApplication(context, metadata, required);
      //最终通过ConditionOutcome.isMatch 方法返回布尔值来确定条件
      if (required && !outcome.isMatch()) {
         return ConditionOutcome.noMatch(outcome.getConditionMessage());
      }
      if (!required && outcome.isMatch()) {
         return ConditionOutcome.noMatch(outcome.getConditionMessage());
      }
      return ConditionOutcome.match(outcome.getConditionMessage());
   }
 
   private ConditionOutcome isWebApplication(ConditionContext context,
         AnnotatedTypeMetadata metadata, boolean required) {
      ConditionMessage.Builder message = ConditionMessage.forCondition(
            ConditionalOnWebApplication.class, required ? "(required)" : "");
      // GenericWebApplicationContext 是否在类路径中
      if (!ClassUtils.isPresent(WEB_CONTEXT_CLASS, context.getClassLoader())) {
         return ConditionOutcome
               .noMatch(message.didNotFind("web application classes").atAll());
      }
      //容器里是否有名为session 的scope;
      if (context.getBeanFactory() != null) {
         String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
         if (ObjectUtils.containsElement(scopes, "session")) {
            return ConditionOutcome.match(message.foundExactly("'session' scope"));
         }
      }
      //当前容器的Enviroment 是否为StandardServletEnvironment
      if (context.getEnvironment() instanceof StandardServletEnvironment) {
         return ConditionOutcome
               .match(message.foundExactly("StandardServletEnvironment"));
      }
      //当前的ResourceLoader 是否为WebApplicationContext ( ResourceLoader 是ApplicationContext 的顶级接口之一):
      if (context.getResourceLoader() instanceof WebApplicationContext) {
         return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
      }
      return ConditionOutcome.noMatch(message.because("not a web application"));
   }
 
}

三、自动配置实例分析

 @ConfigurationProperties(
    prefix = "spring.http.encoding"
)
public class HttpEncodingProperties {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private Charset charset;
    private Boolean force;
    private Boolean forceRequest;
    private Boolean forceResponse;
    private Map<Locale, Charset> mapping;
    ...
}
@Configuration
@EnableConfigurationProperties({HttpEncodingProperties.class}) //1
@ConditionalOnWebApplication  //6
@ConditionalOnClass({CharacterEncodingFilter.class}) //2
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
) //3
public class HttpEncodingAutoConfiguration {
    private final HttpEncodingProperties properties; //4
 
    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
        this.properties = properties;
    }
 
    @Bean  //5
    @ConditionalOnMissingBean({CharacterEncodingFilter.class})
    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 HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
        return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
    }
    ...
}

1)开启属性注入,通过@EnableConfigurationProperties 声明,使用@Autowired 注入;

2)当CharacterEncodingFilter 在类路径的条件下;

3)当设置spring.http.encoding=enabled 的情况下,如果没有设置则默认为true ,即条件符合;

4)像使用Java 配置的方式配置CharacterEncodingFilter 这个Bean。

5)当容器中没有这个Bean 的时候新建Beane

6)在web环境下才创建Bean

四、SpringApplication.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;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
   //①
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      //②
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      // ③
      Banner printedBanner = printBanner(environment);
      // ④
      context = createApplicationContext();
      // ⑤
      analyzers = new FailureAnalyzers(context);
      // ⑥
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
      // ⑦
      refreshContext(context);
      // ⑧
      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, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}

① 通过SpringFactoriesLoader查找并加载所有的SpringApplicationRunListeners,通过调用starting()方法通知所有的SpringApplicationRunListeners,应用开始启动了。其本质上就是一个事件发布者,它在SpringBoot应用启动的不同时间点发布不同应用事件类型(ApplicationEvent),如果有哪些事件监听者(ApplicationListener)对这些事件感兴趣,则可以接收并且处理。还记得初始化流程中,SpringApplication加载了一系列ApplicationListener吗?这个启动流程中没有发现有发布事件的代码,其实都已经在SpringApplicationRunListeners这儿实现了。

简单的分析一下其实现流程,首先看下SpringApplicationRunListener的源码:

SpringApplicationRunListeners

class SpringApplicationRunListeners {
 
   private final Log log;
 
   private final List<SpringApplicationRunListener> listeners;
 
   SpringApplicationRunListeners(Log log,
         Collection<? extends SpringApplicationRunListener> listeners) {
      this.log = log;
      this.listeners = new ArrayList<SpringApplicationRunListener>(listeners);
   }
 
   // 运行run方法时立即调用此方法,可以用于非常早期的初始化工作
   public void starting() {
      for (SpringApplicationRunListener listener : this.listeners) {
         listener.starting();
      }
   }
 
   // Environment准备好后,并且ApplicationContext创建之前调用
   public void environmentPrepared(ConfigurableEnvironment environment) {
      for (SpringApplicationRunListener listener : this.listeners) {
         listener.environmentPrepared(environment);
      }
   }
 
   // ApplicationContext创建好后立即调用
   public void contextPrepared(ConfigurableApplicationContext context) {
      for (SpringApplicationRunListener listener : this.listeners) {
         listener.contextPrepared(context);
      }
   }
 
   // ApplicationContext加载完成,在refresh之前调用
    public void contextLoaded(ConfigurableApplicationContext context) {
      for (SpringApplicationRunListener listener : this.listeners) {
         listener.contextLoaded(context);
      }
   }
 
   // 当run方法结束之前调用
   public void finished(ConfigurableApplicationContext context, Throwable exception) {
      for (SpringApplicationRunListener listener : this.listeners) {
         callFinishedListener(listener, context, exception);
      }
   }
 
   private void callFinishedListener(SpringApplicationRunListener listener,
         ConfigurableApplicationContext context, Throwable exception) {
      try {
         // 发布事件
         listener.finished(context, exception);
      }
      catch (Throwable ex) {
         if (exception == null) {
            ReflectionUtils.rethrowRuntimeException(ex);
         }
         if (this.log.isDebugEnabled()) {
            this.log.error("Error handling failed", ex);
         }
         else {
            String message = ex.getMessage();
            message = (message == null ? "no error message" : message);
            this.log.warn("Error handling failed (" + message + ")");
         }
      }
   }
 
}

② 创建并配置当前应用将要使用的Environment,Environment用于描述应用程序当前的运行环境,其抽象了两个方面的内容:配置文件(profile)和属性(properties)。不同的环境(eg:生产环境、预发布环境)可以使用不同的配置文件,而属性则可以从配置文件、环境变量、命令行参数等来源获取。因此,当Environment准备好后,在整个应用的任何时候,都可以从Environment中获取资源。

总结起来,②处的两句代码,主要完成以下几件事:

  • 判断Environment是否存在,不存在就创建(如果是web项目就创建StandardServletEnvironment,否则创建StandardEnvironment)
  • 配置Environment:配置profile以及properties
  • 调用SpringApplicationRunListener的environmentPrepared()方法,通知事件监听者:应用的Environment已经准备好

③、SpringBoot应用在启动时会输出这样的东西:

ef8dc7bd347e1f06a7c4a722bb9fa877.png

④、根据是否是web项目,来创建不同的ApplicationContext容器。

⑤、创建一系列FailureAnalyzer,创建流程依然是通过SpringFactoriesLoader获取到所有实现FailureAnalyzer接口的class,然后在创建对应的实例。FailureAnalyzer用于分析故障并提供相关诊断信息。

⑥、初始化ApplicationContext,主要完成以下工作:

  • 将准备好的Environment设置给ApplicationContext
  • 遍历调用所有的ApplicationContextInitializer的initialize()方法来对已经创建好的ApplicationContext进行进一步的处理
  • 调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者:ApplicationContext已经准备完毕
  • 将所有的bean加载到容器中
  • 调用SpringApplicationRunListener的contextLoaded()方法,通知所有的监听者:ApplicationContext已经装载完毕

⑦、调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。从名字上理解为刷新容器,那何为刷新?就是插手容器的启动

rehash()方法中一句代码

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

invokeBeanFactoryPostProcessors

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
   PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
 
   // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
   // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
   if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
      beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
      beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
   }
}

获取到所有的BeanFactoryPostProcessor来对容器做一些额外的操作。BeanFactoryPostProcessor允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做一些额外的操作。这里的getBeanFactoryPostProcessors()方法可以获取到3个Processor:

ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor

SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor

ConfigFileApplicationListener$PropertySourceOrderingPostProcessor

不是有那么多BeanFactoryPostProcessor的实现类,为什么这儿只有这3个?因为在初始化流程获取到的各种ApplicationContextInitializer和ApplicationListener中,只有上文3个做了类似于如下操作:

@Override
public void initialize(ConfigurableApplicationContext context) {
   context.addBeanFactoryPostProcessor(
         new ConfigurationWarningsPostProcessor(getChecks()));
}

然后你就可以进入到PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()方法了,这个方法除了会遍历上面的3个BeanFactoryPostProcessor处理外,还会获取类型为BeanDefinitionRegistryPostProcessor的bean:org.springframework.context.annotation.internalConfigurationAnnotationProcessor,对应的Class为ConfigurationClassPostProcessor。

ConfigurationClassPostProcessor用于解析处理各种注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。当处理@import注解的时候,就会调用EnableAutoConfigurationImportSelector.selectImports()来完成自动配置功能。

⑧、查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们。

⑨、执行所有SpringApplicationRunListener的finished()方法。

这就是Spring Boot的整个启动流程,其核心就是在Spring容器初始化并启动的基础上加入各种扩展点,这些扩展点包括:ApplicationContextInitializer、ApplicationListener以及各种BeanFactoryPostProcessor等等。

79cb769fd5749c8e46740e5f5d6e6386.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值