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.