SpringBoot principle in-depth and source code analysis

SpringBoot dependency management

The SpringBoot project indirectly inherits Spring-boot-Dependencies, which provides unified version management for common technical frameworks. Therefore, the SpringBoot project PM. XML introduces the spring-boot-dependencies management of the dependency file does not need to mark the dependency file version number. The introduction of starter enables the development of corresponding scenarios without the need to import additional dependent files.

Automatic configuration (startup process)

SpringBoot application startup entry is the @SpringBootApplication annotation in the main() method of the annotation class. @SpringBootApplication can scan Spring components and automatically configure SpringBoot

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

// Indicate that this class is a configuration class
@SpringBootConfiguration
// Enable automatic configuration
@EnableAutoConfiguration
// Package scanner
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
Copy the code

1. @ SpringBootConfiguration annotation

The @SpringBootConfiguration annotation represents the SpringBootConfiguration class. This annotation is just a simple encapsulation of the @Configuration annotation, which serves the same purpose as the @Configuration annotation.

2. @ EnableAutoConfiguration annotation

@enableAutoConfiguration Indicates that the automatic configuration function is enabled.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// Automatic configuration package
@AutoConfigurationPackage
// Automatically configure class scan import
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<? >[] exclude()default {};
    String[] excludeName() default {};
}
Copy the code
  • @ AutoConfigurationPackage annotations

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    // Import the Registrar component class
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    }
    Copy the code
    static class Registrar implements ImportBeanDefinitionRegistrar.DeterminableImports {
    	// This method imports a concrete implementation of the component class
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // Scan the main program class package and its subpackages into the Spring container
            register(registry, new PackageImport(metadata).getPackageName());
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(newPackageImport(metadata)); }}private static final class PackageImport {
        private final String packageName;
        PackageImport(AnnotationMetadata metadata) {
            // Get the annotation package name
            this.packageName = ClassUtils.getPackageName(metadata.getClassName());
        }
        String getPackageName(a) {
            return this.packageName; }}Copy the code
  • @Import(AutoConfigurationImportSelector.class)

    Will AutoConfigurationImportSelector this class into the Spring container, AutoConfigurationImportSelector can help SpringBoot applications will all eligible configuration is loaded into the current SpringBoot create and use the IoC container (ApplicationContext).

    The selectImports() method in this class tells SpringBoot which components to import through source analysis

    // This method tells SpringBoot which components to import
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // Check whether the enableAutoConfiguration annotation is enabled. It is enabled by default.
        if(! isEnabled(annotationMetadata)) {return NO_IMPORTS;
        }
        //1. Load the meta-INF /spring-autoconfigure-metadata.properties configuration file to obtain all conditions supporting automatic configuration classes
        SpringBoot uses an Annotation handler to collect auto-assembly conditions that can be configured in meta-INF /spring-autoconfigure-metadata.properties.
        // SpringBoot filters the @Configuration collected and removes the Configuration classes that do not meet the requirements
        // The full name of the auto-configured class. Conditions = value
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    Copy the code
    • loadMetadata()

      final class AutoConfigurationMetadataLoader {
          // The file contains the classpath of the configuration class to be loaded
          protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
      
      	private AutoConfigurationMetadataLoader(a) {}public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
              // Override the method
              return loadMetadata(classLoader, PATH);
      	}
          
          static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
              try {
                  //1. Read the spring-autoconfigure-metadata.properties information in the spring-boot-autoconfigure-metadata. jar package to generate urls enumeration objectEnumeration<URL> urls = (classLoader ! =null)? classLoader.getResources(path) : ClassLoader.getSystemResources(path);// Iterate through the URL array and read into properties
                  Properties properties = new Properties();
      
                  //2. Parse the information in the urls enumeration object into a Properties object and load it
                  while (urls.hasMoreElements()) {
                      properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
                  }
                  
      			/ / convert the properties into PropertiesAutoConfigurationMetadata object
      			/ / according to encapsulate good properties object generates AutoConfigurationMetadata object to return
      			return loadMetadata(properties);
      		} catch (IOException ex) {
      			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex); }}static AutoConfigurationMetadata loadMetadata(Properties properties) {
      		return new PropertiesAutoConfigurationMetadata(properties);
      	}
      
      	/ * * * {@link AutoConfigurationMetadata} implementation backed by a properties file.
      	 */
      	private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {
      
              /** * Properties object */
      		private final Properties properties;
      
      		PropertiesAutoConfigurationMetadata(Properties properties) {
      			this.properties = properties;
      		}
      
      		@Override
      		public boolean wasProcessed(String className) {
      			return this.properties.containsKey(className);
      		}
      
      		@Override
      		public Integer getInteger(String className, String key) {
      			return getInteger(className, key, null);
      		}
      
      		@Override
      		public Integer getInteger(String className, String key, Integer defaultValue) {
      			String value = get(className, key);
      			return(value ! =null)? Integer.valueOf(value) : defaultValue; }@Override
      		public Set<String> getSet(String className, String key) {
      			return getSet(className, key, null);
      		}
      
      		@Override
      		public Set<String> getSet(String className, String key, Set<String> defaultValue) {
      			String value = get(className, key);
      			return(value ! =null)? StringUtils.commaDelimitedListToSet(value) : defaultValue; }@Override
      		public String get(String className, String key) {
      			return get(className, key, null);
      		}
      
      		@Override
      		public String get(String className, String key, String defaultValue) {
      			String value = this.properties.getProperty(className + "." + key);
      			return(value ! =null)? value : defaultValue; }}}Copy the code
    • getAutoConfigurationEntry()

      protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
          // 1. Determine whether to enable annotations. If not enabled, an empty string is returned
          if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
          }
          // 2. Get the attributes of the annotation
          AnnotationAttributes attributes = getAttributes(annotationMetadata);
      
          / / 3. GetCandidateConfigurations () is used to obtain the default support automatic configuration of the class list
          // Use the SpringFactoriesLoader, an internal tool class, to search meta-INF /spring.factories on all jars in the classpath.
          / / to find the key for org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration attribute defines the factory class name,
          // Import these values into the container as auto-configuration classes, and the auto-configuration classes take effect
          List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
      
      
          // 3.1 // Remove duplicate configuration classes, if we write our own starter may have duplicate
          configurations = removeDuplicates(configurations);
          // 4. If we do not want automatic configuration of some automatic configuration classes, we can exclude or excludeName of EnableAutoConfiguration.
          / / or also can through the configuration items in the configuration file "spring. Autoconfigure. Exclude" configuration.
          // Find the configuration classes that do not want to be auto-configured (according to the EnableAutoConfiguration annotation, a exclusions attribute)
          Set<String> exclusions = getExclusions(annotationMetadata, attributes);
          Exclusions Class exclusions (Exclusions must be automatically configured, otherwise exceptions will be thrown)
          checkExcludedClasses(configurations, exclusions);
          // 4.2 Removes all configuration classes that do not want automatic configuration from Configurations
          configurations.removeAll(exclusions);
      
          // 5. Filter all candidate automatic configuration classes. According to the dependency files added in the project pom. XML file, the automatic configuration classes corresponding to the current project operating environment are finally selected
      
          ConditionalOnClass: the Bean is instantiated only when a class is on the classpath.
          / / @ ConditionalOnMissingClass: classpath does not exist when the class work
          ConditionalOnBean ConditionalOnBean ConditionalOnBean ConditionalOnBean ConditionalOnBean ConditionalOnBean ConditionalOnBean ConditionalOnBean
          ConditionalOnMissingBean: ConditionalOnMissingBean does not exist in the DI container
          / / @ ConditionalOnSingleCandidate: DI container in this type of Bean has only one or only one of the @ Primary when working
          // @conditionalonexpression: conditionel is true
          // @conditionalonProperty: ConditionalOnProperty is valid when the parameters or values are the same
          ConditionalOnResource: ConditionalOnResource is valid when the specified file exists
          ConditionalOnJndi: ConditionalOnJndi is valid if the specified JNDI exists
          ConditionalOnJava: ConditionalOnJava will work if the specified Java version exists
          / / @ ConditionalOnWebApplication: Web application environment
          / / @ ConditionalOnNotWebApplication: a Web application environment
      
          // Summarize two ways to determine whether to load a class:
          // Use spring-autoconfigure-metadata.properties to judge.
          // Determine if @conditional is satisfied
          / / such as @ ConditionalOnClass ({SqlSessionFactory. Class, SqlSessionFactoryBean. Class}) said to exist in the classpath SqlSessionFactory. Class, SqlSessionFactoryBean. Class these two classes to complete automatic registration.
          configurations = filter(configurations, autoConfigurationMetadata);
      
      
          // 6. Import the automatic configuration event notification listener
          / / when AutoConfigurationImportSelector filter automatically after the completion of loading classpath jars in the meta-inf/spring. Factories file AutoConfigurationImportListener implementation class,
          / / and trigger fireAutoConfigurationImportEvents events.
          fireAutoConfigurationImportEvents(configurations, exclusions);
          // 7. Create an AutoConfigurationEntry
          return new AutoConfigurationEntry(configurations, exclusions);
      }
      Copy the code
      • getCandidateConfigurations()

        protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            // Let SpringFactoryLoader load some component names
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
            // assert, not null
            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;
        }
        Copy the code
        • loadFactoryNames()

          public static List<String> loadFactoryNames(Class
                          factoryClass, @Nullable ClassLoader classLoader) {
              String factoryClassName = factoryClass.getName();
              return loadSpringFactories(classLoader).getOrDefault( factoryClassName, Collections.emptyList());
          }
          
          	private static Map<String, List<String>> loadSpringFactories( @Nullable ClassLoader classLoader) {
                  MultiValueMap<String, String> result = cache.get(classLoader);
                  if(result ! =null) {
                      return result;
                  }
                  
                  try {
                      // If the classloader is not null, then the spring.factories under the classpath are loaded to encapsulate the full path information of the configuration class as an Enumeration object
                      // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";Enumeration<URL> urls = (classLoader ! =null ?
          classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : 
          ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
          			result = new LinkedMultiValueMap<>();
                      // Generate the Properties object according to the corresponding node information, and obtain the value through the passed key. In the result set, the value is cut into small strings and converted into Array
          			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); }}Copy the code

    The @enableAutoConfiguration command is used to search the mate-INF/Spring. factories configuration file from the classpath. And will it org. Springframework. Boot. Autoconfigure. EnableutoConfiguration corresponding Configuration items through reflection sample into the corresponding annotation @ Configuration JavaConfig Configuration in the form of class, And loaded into the IoC container.

Conclusion:

The underlying Springboot auto-assembly steps are:

  1. Springboot Starts the application.
  2. @SpringBootApplicationWork;
  3. @EnableAutoConfiguration;
  4. @AutoConfigurationPackage:This combination of annotations is mainly@Import(AutoConfigurationPackages.Registrar.class), it passes willRegistrarClass is imported into the container, andRegistrarClass is used to scan directories and subpackages of the main configuration class and import the corresponding components into the Springboot creation management container
  5. @Import(AutoConfigurationImportSelector.class):It will throughAutoConfigurationImportSelectorClass import into container,AutoConfigurationImportSelectorThe class is used to passselectImportsInternal utility classes are used in the process of method thinkingSpringFactoriesLoaderTo find theclasspathIn the jar package used onMATE-INF/spring.factoriesLoad, the realization of the configuration class information toSpringFactoryThe loader performs a series of container creation processes.

3. @ ComponentScan annotation

The root path of a package to be scanned is determined by the location of the package in which the Spring Boot project’s main startup class is located, and is resolved during scanning by the @AutoConfigurationPackage annotation described earlier. To get the Springboot project main program boot class package location.

Conclusion:

|- @SpringBootConfiguration
	|- @Configuration // Add components to the IoC container via javaConfig
|- @EnableAutoConfiguration
    |- @AutoConfigurationPackage // Automatic configuration package, with @ComponentScan scanned to add to IOC
    |- @Import(AutoConfigurationImportSelector.class) // Add beans defined in meta-INF/spring.Factories to the IoC container
|- @ComponentScan / / package scans
Copy the code

Custom Starter

Starter enables developers to use a function without paying attention to the processing of various dependent libraries or requiring specific configuration information. Springboot automatically finds the required beans through classes in the classpath path and weaves them into the corresponding beans.

steps

  1. * Spring-boot-starter * spring-boot-autoconfigure * spring-boot-autoconfigure * spring-boot-autoconfigure

  2. Writing configuration classes

    • @Configuration
    • @ConditionalOnXxx: Indicates automatic configuration conditions
  3. Use the/meta-INF /spring.factories file under Resources to configure the custom configuration classes

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.test.config.MyAutoConfiguration
    Copy the code
  4. Test using

How springApplication.run () works

The source code

/ / call the static class, parameter matching is SpringbootDemoApplication. The class as well as the main method of the args
public static ConfigurableApplicationContext run(Class
        primarySource, String... args) {
    return run(newClass<? >[] { primarySource }, args); }public static ConfigurableApplicationContext run(Class
       [] primarySources, String[] args) {
    // The SpringApplication startup consists of two parts:
    //1. Instantiate the SpringApplication object
    //2. run(args) : call the run method
    return new SpringApplication(primarySources).run(args);
}
Copy the code

1. Initialization of the SpringApplication instance

public SpringApplication(Class
       ... primarySources) {
    this(null, primarySources);
}

@SuppressWarnings({ "unchecked"."rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class
       ... primarySources) {
    this.sources = new LinkedHashSet();
    / / Banner pattern
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    // Whether to add JVM startup parameters
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = false;
    // Resource loader
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");

    / / project startup SpringbootDemoApplication. Class is set to the attribute stored
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

    // Set the application type to SERVLET application (traditional MVC application before Spring 5) or REACTIVE application (WebFlux interactive application since Spring 5)
    The deduceFromClasspath() method is used to determine the webApplicationType by checking whether there is a characteristic class in the ClassPath
    this.webApplicationType = WebApplicationType.deduceFromClasspath();

    // Set initializers, which will eventually be called
    / / the so-called initializer is org. Springframework. Context. ApplicationContextInitializer implementation class, initialized before the Spring context is refresh operation
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

    // Set the Listener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    // Initializes the mainApplicationClass property: used to infer and set the main program launcher class started by the project main() method
    this.mainApplicationClass = deduceMainApplicationClass();
}
Copy the code

2. The project is initialized and started

public ConfigurableApplicationContext run(String... args) {
    // Create a StopWatch object and start it. StopWatch collects statistics about the run startup duration.
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // Initializes the application context and exception report collection
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // Configure the headless attribute
    configureHeadlessProperty();

    // (1) Get and start listener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        // create an ApplicationArguments object to initialize the default ApplicationArguments class
        // Args is the command line argument that starts the Spring application and can be accessed in the Spring application. Such as: -- server. The port = 9000
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // (2) Pre-configuration of the project operation Environment
        // Create and configure the Environment to be used by the current SpringBoot application
        / / and traverse call all SpringApplicationRunListener environmentPrepared () method
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // Eliminate unneeded runtime environments
        configureIgnoreBeanInfo(environment);
        // Prepare the Banner printer - is the ASCII art font printed on the console when Spring Boot is started
        Banner printedBanner = printBanner(environment);

        // Create a Spring container
        // Determine the container type according to webApplicationType. If the container type is SERVLET, the corresponding bytecode will be reflected. AnnotationConfigServletWebServerApplicationContext, Context, environment, Listeners, and applicationArgument are then used to assemble and configure the application context
        context = createApplicationContext();
        / / get abnormality report SpringBootExceptionReporter array
        // The logic of this step is the same as instantiating initializers and listeners,
        / / is by calling getSpringFactoriesInstances exception class name of the method to get the configuration and instantiate all exception handling classes.
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);


        // (4) Spring container preprocessing
        // This step is mainly a preparatory action before the container is refreshed. This includes a very critical operation: injecting the startup class into the container to set the stage for enabling automated configuration later.
        prepareContext(context, environment, listeners, applicationArguments,
                       printedBanner);

        // (5) : refresh the container
        // Start (refresh) the Spring container, initialize the entire IoC container (including Bean resource location, resolution, registration, etc.) through the refresh method, and register a shutdown hook with the JVM runtime to close the context when the JVM shuts down, unless it is already closed
        refreshContext(context);

        // (6) : Spring container postprocessing
        // Extension interface, template method in design mode, default empty implementation.
        // You can override this method if you have custom requirements. Such as printing some start-end logs, or some other post-processing
        afterRefresh(context, applicationArguments);
        // Stop StopWatch Statistics duration
        stopWatch.stop();
        // Prints the Spring Boot startup duration log.
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // (7) Issue an event notification to end execution
        listeners.started(context);

        // (8) : execute Runners
        // Used to call the custom executor XxxRunner class in the project to execute specific programs immediately after the project is started
        // The Runner Runner is used to perform some business initialization operations when the service is started. These operations are performed only once after the service is started.
        Spring Boot provides two service interfaces, ApplicationRunner and CommandLineRunner
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        // If an exception occurs, handle it and throw an IllegalStateException
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    // (9) Publish the application context ready event
    / / said in front of all initialization start without a problem, use run listener SpringApplicationRunListener run continuously configured application context ApplicationContext,
    // The whole Spring Boot project is officially started.
    try {
        listeners.running(context);
    } catch (Throwable ex) {
        // If an exception occurs, handle it and throw an IllegalStateException
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    // Return the container
    return context;
}
Copy the code