Understand the principle of springboot startup through code examples

Time:2021-4-19

This article mainly introduces how to understand the spring boot startup principle through code examples. The example code is introduced in great detail, which has a certain reference learning value for everyone’s study or work. Friends in need can refer to it

Compared with spring, springboot has many advantages, such as automatic configuration, jar running directly and so on. So how does springboot start?

Here is the entry to start springboot:


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

1、 Let’s take a look at the @ springboot annotation:

@Target({ ElementType.TYPE }) // define when to use it
@Retention( RetentionPolicy.RUNTIME )// the compiler stores annotation in the class file, which can be read and used by VM code using reflection mechanism.
@Documented // this annotation should be recorded by the Javadoc tool
@Inherited // the annotated class will inherit automatically. More specifically, if the @ inherited tag is used when defining an annotation, and then another parent class is annotated with the defined annotation, and the parent class has a subclass, then all properties of the parent class will be inherited to its subclass
@Springbootconfiguration // @ springbootconfiguration is equivalent to @ configuration. Javaconfig configuration form
@EnableAutoConfiguration
@ComponentScan(
  excludeFilters = {@Filter(
  type = FilterType.CUSTOM,
  classes = {TypeExcludeFilter.class}
), @Filter(
  type = FilterType.CUSTOM,
  classes = {AutoConfigurationExcludeFilter.class}
)}// automatically scan and load qualified components. To fine-grained customize the scope of @ componentscan automatic scanning through attributes such as basepackages. If not specified, the default spring framework implementation will scan from the package of the class where @ componentscan is declared.
Note: therefore, the startup class of springboot is best placed under root package, because basepackages is not specified by default.
)
public @interface SpringBootApplication {
  @AliasFor(
    annotation = EnableAutoConfiguration.class
  )
  Class<?>[] exclude() default {};

  @AliasFor(
    annotation = EnableAutoConfiguration.class
  )
  String[] excludeName() default {};

  @AliasFor(
    annotation = ComponentScan.class,
    attribute = "basePackages"
  )
  String[] scanBasePackages() default {};

  @AliasFor(
    annotation = ComponentScan.class,
    attribute = "basePackageClasses"
  )
  Class<?>[] scanBasePackageClasses() default {};
}

So, in fact, the springbootapplication annotation is equivalent to a combination of three annotations, @ springbootconfiguration, @ componentscan and @ enableautoconfiguration.

@Spring bootconfiguration and @ componentscan, it’s easy to know what they mean, one is javaconfig configuration, the other is scan package. The key is the @ enableautoconfiguration annotation. Let’s take a look at this note


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
  String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

  Class<?>[] exclude() default {};

  String[] excludeName() default {};
}

When using the configurationclassparser to analyze the configuration class during springboot application startup, if @ import (importselector) is found in the annotation, a corresponding importselector object will be created and its method public string [] selectimports (annotationmetadata annotationmetadata) will be called, Here, the import of the enableautoconfigurationimportselector @ import (enableautoconfigu rationImportSelector.class )This is the case, so the configurationclassparser instantiates an enableautoconfigurationimportselector and calls its selectimports () method.

AutoConfigurationImportSelector implements DeferredImportSelector extends ImportSelector。

The following is the execution process of autoconfigurationimportselector:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
  private static final String[] NO_IMPORTS = new String[0];
  private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
  private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
  private ConfigurableListableBeanFactory beanFactory;
  private Environment environment;
  private ClassLoader beanClassLoader;
  private ResourceLoader resourceLoader;
  public AutoConfigurationImportSelector() {
  }

  public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
    } else {
      //Loading autoconfigurationmetadata from a configuration file
      AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
      AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
      //Get all candidate configuration classes, enableautoconfiguration
      //The internal tool is used to find all the jars in the classpath using spring factors loader
      // META-INF\ spring.factories Find out where key is
      // org.springframework.boot.autoconfigure.EnableAutoConfiguration 
      //The name of the factory class defined by the property of.
      //Although the parameters include annotationmetadata and attributes, they can be found in the
      //These two parameters are not used in the implementation of getcandidateconfigurations()
      List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
      //De duplication
      configurations = this.removeDuplicates(configurations);
      Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
      //Apply the exclusion property
      this.checkExcludedClasses(configurations, exclusions);
      configurations.removeAll(exclusions);
      //Application filter autoconfigurationimportfilter,
      //For spring boot autoconfiguration, a filter to be applied is defined
      // org.springframework.boot.autoconfigure.condition.OnClassCondition,
      //This filter checks the annotation @ conditionalonclass on the candidate configuration class if the required class is in classpath
      //If no, the candidate configuration class will be excluded
      configurations = this.filter(configurations, autoConfigurationMetadata);
     //Now we have found all the candidate configuration classes that need to be applied
     //Broadcast event autoconfigurationimportevent
      this.fireAutoConfigurationImportEvents(configurations, exclusions);
      return StringUtils.toStringArray(configurations);
    }
  }

  protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
    String name = this.getAnnotationClass().getName();
    AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
    Assert.notNull(attributes, () -> {
      return "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?";
    });
    return attributes;
  }

  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
  }


public abstract class SpringFactoriesLoader {
  public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
  private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();

  public SpringFactoriesLoader() {
  }
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  }
}

  private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    //Record whether candidate configuration classes need to be excluded. Skip is true, which means they need to be excluded. All of them are initialized to false and do not need to be excluded
    boolean[] skip = new boolean[candidates.length];
    //Record whether any candidate configuration class is ignored and initialize to false
    boolean skipped = false;
    Iterator var8 = this.getAutoConfigurationImportFilters().iterator();
    //Get autoconfigurationimportfilter and apply the filter one by one
    while(var8.hasNext()) {
      AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next();
      //Inject the filter with the information it needs aware
      this.invokeAwareMethods(filter);
      //Use this filter to check the match between candidate configuration classes and autoconfigurationmetadata
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);

      for(int i = 0; i < match.length; ++i) {
        if (!match[i]) {
         //If a candidate configuration class does not meet the current filter, it will be marked as needing to be excluded,
    //And skiped is set to true, which means that a candidate configuration class needs to be excluded
          skip[i] = true;
          skipped = true;
        }
      }
    }

    if (!skipped) {
    //If all candidate configuration classes do not need to be excluded, the set of candidate configuration classes provided by external parameters will be returned directly
      return configurations;
    } else {
    //Here comes the logic, because skipped is true, which indicates that some candidate configuration classes have been found in the above filter application logic
    //The candidate configuration classes that need to be excluded are excluded. The candidate configuration classes that do not need to be excluded are composed
    //A new collection is returned to the caller
      List<String> result = new ArrayList(candidates.length);

      int numberFiltered;
      for(numberFiltered = 0; numberFiltered < candidates.length; ++numberFiltered) {
        if (!skip[numberFiltered]) {
          result.add(candidates[numberFiltered]);
        }
      }

      if (logger.isTraceEnabled()) {
        numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
      }

      return new ArrayList(result);
    }
  }

  /**
   *Use the internal tool springfactoryesloader to find all jar packages in the classpath
   * META-INF\ spring.factories Find out where key is
   * org.springframework.boot.autoconfigure.AutoConfigurationImportFilter 
   *And instantiate.
   *Autoconfigurationimportfilter filters can be registered with spring.factories Used to configure classes automatically
   *Make some restrictions and quickly exclude the bytecodes of these auto configuration classes before they are read.
   *Spring boot autoconfiguration registers an autoconfigurationimportfilter by default
   * org.springframework.boot.autoconfigure.condition.OnClassCondition
  **/
  protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
  }

2、 Let’s take a look at the execution of the run method when springboot starts

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  this.configureHeadlessProperty();
  //Download meta-inf from classpath/ spring.factories obtain 
  SpringApplicationRunListeners listeners = getRunListeners(args);
  //Call back all the data to get springap plicationRunListener.starting () method
  listeners.starting();
  try {
    //Encapsulating command line parameters
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
          args);
    //Prepare environment 
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
          applicationArguments);
    //Callback after creating environment 
    SpringAp plicationRunListener.environmentPrepared (); indicates that the environment is ready
    this.configureIgnoreBeanInfo(environment);
    //Print banner map
    Banner printedBanner = printBanner(environment);
    //Create an ApplicationContext to decide whether to create a web IOC or an ordinary IOC 
    context = createApplicationContext();
    //Anomaly analysis report
    exceptionReporters = getSpringFactoriesInstances(
          SpringBootExceptionReporter.class,
          new Class[] { ConfigurableApplicationContext.class }, context);
    //Prepare the context and save the environment to IOC
    //Applyinitializers(): call back the initialize method of all the applicationcontextinitializers saved before 
    //listeners.contextPrepared(context) 
    //After the preparecontext runs, call back the contextloaded() of all spring applicationrunlisteners
    this.prepareContext(context, environment, listeners, applicationArguments,
          printedBanner);
    //Refresh the container and initialize the IOC container (if it is a web application, it will also create an embedded Tomcat)
    //Scan, create, load all components, (configuration class, component, auto configuration)
    this.refreshContext(context);
    this.afterRefresh(context, applicationArguments);
    stopWatch.stop();
    if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass)
              .logStarted(getApplicationLog(), stopWatch);
    }
    //All spring application runlistener callbacks start method
    listeners.started(context);
    //Get all the applicationrunners and commandlinerunners from the IOC container for callback,
    //Applicationrunner calls back first, and commandlinerunner calls back again
    this.callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
    this.handleRunFailure(context, ex, exceptionReporters, listeners);
    throw new IllegalStateException(ex);
  }
  try {
    //All springapplicationrunlistener callbacks to the running method
    listeners.running(context);
  }
  catch (Throwable ex) {
    this.handleRunFailure(context, ex, exceptionReporters, null);
    throw new IllegalStateException(ex);
  }
  //After the whole springboot application is started, it returns to the started IOC container
  return context;
}

The above is the whole content of this article, I hope to help you learn, and I hope you can support developer more.

Recommended Today

Review of SQL Sever basic command

catalogue preface Installation of virtual machine Commands and operations Basic command syntax Case sensitive SQL keyword and function name Column and Index Names alias Too long to see? Space Database connection Connection of SSMS Connection of command line Database operation establish delete constraint integrity constraint Common constraints NOT NULL UNIQUE PRIMARY KEY FOREIGN KEY DEFAULT […]