Startup Principle of SpringBoot Analysis-SpringApplication

Keywords: Spring SpringBoot Java xml

Start with a simple SpringBoot demo

package com.baojiong.demo;

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

@SpringBootApplication
public class DemoApplication {

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

}

As this simple applet above shows that the Springboot program was started mainly through SpringApplication.run(), let's analyze how Springboot was started through the SpringApplication.run() method.

Constructor

/**
 * Create a new {@link SpringApplication} instance. The application context will load
 * beans from the specified primary sources (see {@link SpringApplication class-level}
 * documentation for details. The instance can be customized before calling
 * {@link #run(String...)}.
 * @param resourceLoader the resource loader to use
 * @param primarySources the primary bean sources
 * @see #run(Class, String[])
 * @see #setSources(Set)
 */
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    //Assertion initiates the main class, and with variable parameters in the method you can see that the main class can be multiple
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //Determine if this is a web environment
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //Load the ApplicationContextInitializer interface class
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //Load the ApplicationListener class
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //Find the startup class by including the main method or not
    this.mainApplicationClass = deduceMainApplicationClass();
}

Next, let's look at each method

deduceFromClasspath()

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

Static methods return specific types by determining if there is a corresponding class in the classpath

  • WebApplicationType.REACTIVE reactive web application (if org.springframework.web.reactive.DispatcherHandler exists)
  • WebApplicationType.SERVLET servlet web application (if javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext exists)
  • WebApplicationType.NONE Simple java Application

getSpringFactoriesInstances()

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //Instantiate by reflection
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

Mainly through the SpringFactoriesLoader.loadFactoryNames() method, tracing its source code reveals key points in SpringFactoriesLoader.loadSpringFactories()

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

SpringFactoriesLoader.loadSpringFactories()

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

    try {
        //Search for "META-INF/spring.factories" file under classpath
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        //Classes configured in the file are added to the collection as classifications
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

From the above methods, it can be seen that springboot is mainly implemented by default through the "META-INF/spring.factories" configuration convention.ApplicationContextInitializer and ApplicationListener are also configured accordingly.

From the SpringApplication constructor, you can see that some of the initialization operations are mainly completed by instantiating the Application ContextInitializer and ApplicationListener interface implementations.

SpringApplication.run()

After analyzing the constructor, let's look at the most important SpringApplication.run() method.

public ConfigurableApplicationContext run(String... args) {
    //Start Timing
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    //Configure headless parameters
    configureHeadlessProperty();
    //Get the SpringApplicationRunListener interface and instantiate it
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //Call start()
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // Publish ApplicationStartedEvent Event Events
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

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

Separate analysis of specific methods

getRunListeners()

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger,
            getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

From the source code, you can see that the main call is to get and instantiate the SpringApplicationRunListener interface implementation class from META-INF/spring.factories by calling the getSpringFactoriesInstances() method that has been analyzed in the SpringApplication constructor.The configuration file shows the specific org.springframework.boot.context.event.EventPublishingRunListener.Analyze Event Publishing RunListener

public EventPublishingRunListener(SpringApplication application, String[] args) {
    this.application = application;
    this.args = args;
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    for (ApplicationListener<?> listener : application.getListeners()) {
        this.initialMulticaster.addApplicationListener(listener);
    }
}

From the EventPublishingRunListener constructor, you can see that a Lister of ApplicationListener type is obtained primarily through application.getListeners() and then added to the SimpleApplicationEventMulticaster through this.initialMulticaster.addApplicationListener(listener) in the following context.

listeners.starting();

@Override
public void starting() {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

Then when the starting method of EventPublishingRunListener is called, a listener supporting the Application Starting Event event is found to call its onApplicationEvent event method.The main listeners are the following:

prepareEnvironment()

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    // Gets the Configurable Environment of the specified type from the webApplicationType (in this case, StandardServletEnvironment)
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // Parse args parameters and read spring.profile.active
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    // Execute the ApplicationEnvironmentPreparedEvent event
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

Prepare the environment; listeners.environment Prepared; code is mainly used to publish the ApplicationEnvironmentPreparedEvent event, one of the more important listeners ConfigFileApplicationListener executes the onApplicationEvent method and loads the corresponding properties or yml configuration file into the environment based on spring.profile.active.

configureIgnoreBeanInfo()

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {      
    if(System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {            
        Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);            
        System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());      
    }
}

The main purpose is to read the spring.beaninfo.ignore property and set the environment parameter to true.

printBanner()

private Banner printBanner(ConfigurableEnvironment environment) {
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }
    ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
            : new DefaultResourceLoader(getClassLoader());
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
    if (this.bannerMode == Mode.LOG) {
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

Print the banner diagram.

createApplicationContext()

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

Based on the corresponding Configurable ApplicationContext for the application type instance, this example returns the org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext

prepareContext()

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    // The ApplicationContextInitializer interface implements the class to execute the initialize() method
    applyInitializers(context);
    // Publish the ApplicationContextInitializedEvent event
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    // Loading beans
    load(context, sources.toArray(new Object[0]));
    // Publish ApplicationPreparedEvent Event Event Event
    listeners.contextLoaded(context);
}

Take a closer look at the load(ApplicationContext context, Object[] sources) method

load(ApplicationContext context, Object[] sources)

/**
 * Load beans into the application context.
 * @param context the context to load beans into
 * @param sources the sources to load
 */
protected void load(ApplicationContext context, Object[] sources) {
	if (logger.isDebugEnabled()) {
		logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
	}
	BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
	if (this.beanNameGenerator != null) {
		loader.setBeanNameGenerator(this.beanNameGenerator);
	}
	if (this.resourceLoader != null) {
		loader.setResourceLoader(this.resourceLoader);
	}
	if (this.environment != null) {
		loader.setEnvironment(this.environment);
	}
	loader.load();
}
createBeanDefinitionLoader()
/**
 * Factory method used to create the {@link BeanDefinitionLoader}.
 * @param registry the bean definition registry
 * @param sources the sources to load
 * @return the {@link BeanDefinitionLoader} that will be used to load beans
 */
protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
	return new BeanDefinitionLoader(registry, sources);
}

// BeanDefinitionLoader constructor
/**
 * Create a new {@link BeanDefinitionLoader} that will load beans into the specified
 * {@link BeanDefinitionRegistry}.
 * @param registry the bean definition registry that will contain the loaded beans
 * @param sources the bean sources
 */
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
	Assert.notNull(registry, "Registry must not be null");
	Assert.notEmpty(sources, "Sources must not be empty");
	this.sources = sources;
    // Annotation Resolution Class
	this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
    // xml parsing class
	this.xmlReader = new XmlBeanDefinitionReader(registry);
	if (isGroovyPresent()) {
		this.groovyReader = new GroovyBeanDefinitionReader(registry);
	}
    // Mask Specified Class
	this.scanner = new ClassPathBeanDefinitionScanner(registry);
	this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

Look back at loader.load()

/**
 * Load the sources into the reader.
 * @return the number of loaded beans
 */
int load() {
	int count = 0;
	for (Object source : this.sources) {
		count += load(source);
	}
	return count;
}

private int load(Object source) {
	Assert.notNull(source, "Source must not be null");
	if (source instanceof Class<?>) {
		return load((Class<?>) source);
	}
	if (source instanceof Resource) {
		return load((Resource) source);
	}
	if (source instanceof Package) {
		return load((Package) source);
	}
	if (source instanceof CharSequence) {
		return load((CharSequence) source);
	}
	throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

private int load(Class<?> source) {
	if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
		// Any GroovyLoaders added in beans{} DSL can contribute beans here
		GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
		load(loader);
	}
	if (isComponent(source)) {
		this.annotatedReader.register(source);
		return 1;
	}
	return 0;
}

The main purpose here is to determine whether the set source class is annotated by @Component and, if so, injected into the beanFactory.The @SpringApplication annotation is used in this example, so it will eventually be processed by AnnotatedBeanDefinitionReader.

refreshContext

Do a context refresh job, too much code, plan to open a separate article for interpretation.

callRunners()

Call the CommandLineRunner interface to implement the run() method of the class

Summary

Looking at the entire Springboot startup source, we can see whether it is critical that springboot loads the corresponding implementation classes from the spring.factories configuration, and from the official springboot documentation, we can see that if we need to, we can also add the corresponding configuration in the spring.factories to achieve the corresponding purpose.

Three original articles were published. 1. Visits 1410
Private letter follow

Posted by cute_girl on Wed, 11 Mar 2020 20:15:14 -0700