Initialization of the SpringApplication
Having examined the @SpringBootApplication annotation on the bootstrap class,
Then continue to analyze the main method, just call a SpringApplication. Run (SpringbootApplication. Class, args), started the web container, we see the run method do
public static ConfigurableApplicationContext run(Class
[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
Copy the code
As you can see, the SpringApplication is initialized and its run method is executed.
Starting from the constructor of SpringApplication, the constructor of SpringApplication mainly does four things:
- Configuration source
- Infer the Web application type
- Apply context initiators and apply event listeners
- Derive references to the main class
The source code is as follows:
@SuppressWarnings({ "unchecked"."rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class
... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
/ / 1. Configuration source
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//2. Infer the Web application type
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//3. Apply context initializer and apply event listener
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//4. Derive references to the main class
this.mainApplicationClass = deduceMainApplicationClass();
}
Copy the code
Configuration source
Configuration springApplication start class, in the form of Java configuration we are very familiar with, direct use: springApplication. Run (MySpringBootApplication. Class, args); , including MySpringBootApplication. Class is the second parameter primarySources constructors
XML configuration:
SpringApplication springApplication = new SpringApplication();
springApplication.setSources();
springApplication.run(args);
Copy the code
This is done by adding parameters to the setSources method, such as the class name, package name, and XML path
PrimarySources is a set of data sources used by Spring Boot.
public Set<Object> getAllSources(a) {
Set<Object> allSources = new LinkedHashSet<>();
if(! CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources); // The source configured in Java mode
}
if(! CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources); // The source configured in XML mode
}
return Collections.unmodifiableSet(allSources);
}
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, //..... // Load the sources Set;
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
Copy the code
The prepareContext method is called from the SpringApplication’s Run method, and as you can see, sources is passed to the load method.
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();
}
Copy the code
B: well… Follow up:
protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}
Copy the code
Look for BeanDefinitionLoader constructor:
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
this.xmlReader = new XmlBeanDefinitionReader(registry);
if (isGroovyPresent()) {
this.groovyReader = new GroovyBeanDefinitionReader(registry);
}
this.scanner = new ClassPathBeanDefinitionScanner(registry);
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
Copy the code
As you can see, respectively by AnnotatedBeanDefinitionReader and XmlBeanDefinitionReader these two classes to read and will load for the Spring Bean configuration source. This also shows that there can be two data sources during the SpringBoot configuration phase.
Infer the Web application type and main bootstrap class
Spring Boot can be used not only as a WEB project, but also as a non-Web project, so how does SpringBoot infer the application type? The answer is to infer the WEB application type based on whether the relevant implementation class exists in the current application ClassPath. Types are defined in the enumeration class WebApplicationType, and there are three types:
Web Reactive: enumerated types for WebApplicationType Reactive, corresponding to the class path: org. Springframework. Web. Reactive. DispatcherHandler
Web Servlet: enumerated types for WebApplicationType Servlet, the corresponding class path: org. Springframework. Web. Servlet. DispatcherServlet
Non-web: The enumeration type is WebApplicationType.None
DeduceFromClasspath determines the type in WebApplicationType:
static WebApplicationType deduceFromClasspath(a) {
/ / in the presence of classpath Web Reactive class path and there is no WebApplicationType. SERVLET class path is enabled
/ / WEB Reactive container
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// Non-Web types are enabled when none exist on the classpath
for (String className : SERVLET_INDICATOR_CLASSES) {
if(! ClassUtils.isPresent(className,null)) {
returnWebApplicationType.NONE; }}// The Tomcat container is enabled by default
return WebApplicationType.SERVLET;
}
Copy the code
Apply context initiators and apply event listeners
The following two methods are called to implement the initialization context and event listener:
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
Copy the code
Both initialization of the same, so you just need to pick just one to see, here see the initial application context, first by getSpringFactoriesInstances (ApplicationContextInitializer. Class) returns a collection of As you can guess from the name of this method, the factory mode is used to initialize the application context.
private <T> Collection<T> getSpringFactoriesInstances(Class
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));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
Copy the code
First look at the through SpringFactoriesLoader. LoadFactoryNames (type, this) this method, to obtain a set collection, introduced to the two parameters, First is ApplicationContextInitializer. The class, the second is the class loader, look into the method:
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryType the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @throws IllegalArgumentException if an error occurs while loading factory names
* @see #loadFactories
*/
public static List<String> loadFactoryNames(Class
factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
Copy the code
What can we learn through annotation, this method for inheritance from # FACTORIES_RESOURCE_LOCATION ApplicationContextInitializer of this class of all classes of the fully qualified class name, Where #FACTORIES_RESOURCE_LOCATION is a string constant: meta-INF /spring.factories
The content of this spring.factories looks like this:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
Copy the code
Parsing the contents of this file to get the fully qualified class name returns a Set of strings, which is the Set< string > names we saw at the beginning.
And then passed to the createSpringFactoriesInstances method, an instance and then sort
private <T> List<T> createSpringFactoriesInstances(Class
type, Class
[] parameterTypes, ClassLoader classLoader, Object[] args, Set
names)
{
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try{ Class<? > instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<? > constructor = instanceClass.getDeclaredConstructor(parameterTypes); T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); }catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + ":"+ name, ex); }}return instances;
}
Copy the code
Derive references to the main class
As mentioned earlier, we usually write the boot class code for SpringBoot as follows:
@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) { SpringApplication.run(MySpringBootApplication.class, args); }}Copy the code
We can clearly see that the main class is MySpringBootApplication, but SpringBoot can be launched in multiple ways, and the first argument to SpringApplication.run is variable, that is, I can start MySpringBootApplication by calling springApplication. run in the main method of another class, then that class is the main class, not MySpringBootApplication:
public class App {
public static void main(String[] args) { SpringApplication .run(MySpringBootApplication.class,args); }}Copy the code
The method derived from the reference to the main class is called in the constructor of SpringBoot:
privateClass<? > deduceMainApplicationClass() {try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
returnClass.forName(stackTraceElement.getClassName()); }}}catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
Copy the code
Debug check:
As you can see, SpringBoot iterates through the exception stack to determine the main method, and finds that the Chinese method name of the class named App is main, thus deriving App as the main class.
So SpringBoot’s boot main class is derived by SpringBoot itself, not passed on by us.