In-depth analysis of the underlying source code of Springboot startup principle

Reprinted from: https://blog.csdn.net/weixin_43570367/article/details/104960677

Article Directory

 

1. An analysis of the entry class and its source code

Entry class

@SpringBootApplication
public class DevServiceApplication {

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

Insert picture description here
Start with annotations and analyze:

@SpringBootApplication annotation

The Spring Boot application annotation on a certain class indicates that this class is the main configuration class of SpringBoot, and SpringBoot should run the main method of this class to start the SpringBoot application

源码剖析

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

As can be seen from the source code, this annotation is a combination of the three annotations @SpringBootConfiguration, @EnableAutoConfiguration and @ComponentScan

① @SpringBootConfiguration

The configuration class of Spring Boot; marked on a certain class, which means that a class provides a Spring Boot application

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

@ Configuration : Annotate this annotation on the configuration class;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

Note :

The configuration class is equivalent to the configuration file; the configuration class is also a component in the container, which uses the @Component annotation.

② @EnableAutoConfiguration

Tell SpringBoot to turn on the automatic configuration function, so that the automatic configuration can take effect.
With the help of @import, scan and instantiate the automatically configured beans that meet the conditions, and then load them into the IOC container

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

@ AutoConfigurationPackage : Automatic configuration package
@ Import (EnableAutoConfigurationImportSelector.class): Import components into the container

Insert picture description here

Use the @EnableAutoConfiguration
annotation to turn on automatic scanning, then use select to select files that meet the conditions, and use SpringFactoriesLoader for instantiation. Finally, it is loaded into the IOC container, which is the ApplicationContext.

③ @ComponentScan

@ComponentScan is to automatically scan and load qualified components (such as @Component and @Repository, etc.) or bean definitions, and finally load these bean definitions into the IOC container.

Second, the source code analysis of the instantiated SpringApplication object

源码剖析

/**
 * 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)
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	// 初始化资源加载器
	this.resourceLoader = resourceLoader;
	// 资源加载类不能为 null
	Assert.notNull(primarySources, "PrimarySources must not be null");
	// 初始化加载资源类集合并去重
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	// 推断应用程序是不是web应用
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	// 设置初始化器(Initializer)
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	// 设置监听器 
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	// 推断出主应用入口类
	this.mainApplicationClass = deduceMainApplicationClass();
}

Among them, the deduceFromClasspath() method is called when inferring whether the application is a web application

源码剖析

static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			// springboot2.0提出的响应式web应用	
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			// 如果两个包路径都没有的话,就是普通应用
			if (!ClassUtils.isPresent(className, null)) {
				// 普通的应用
				return WebApplicationType.NONE;
			}
		}
		// 其实最后返回的就是这个servlet,因为是web应用
		return WebApplicationType.SERVLET;
	}

1. Set up the initializer (Initializer)

initializers is an instance attribute in SpringApplication

源码剖析

/**
 * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
 * {@link ApplicationContext}.
 * @param initializers the initializers to set
 */
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
	this.initializers = new ArrayList<>(initializers);
}

initailizer implements the ApplicationContextInitializer interface

源码剖析

to sum up:

  • The role of the ApplicationContextInitializer interface is to initialize the Spring context before it is refreshed. Typically, such as in web applications, registering Property Sources or activating Profiles. Property Sources is easier to understand, which is the configuration file. Profiles is an entity abstracted by Spring in order to load different configuration items in different environments (such as DEV, TEST, PRODUCTION, etc.).
  • Call the initialize() method to load the initialized ApplicationContextInitializer implementation into SpringApplication


Get the implementation class through the getSpringFactoriesInstances( ApplicationContextInitializer.class) method

源码剖析

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
	return getSpringFactoriesInstances(type, new Class<?>[] {});
}

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保存names
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	// 根据names进行实例化
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	// 对实例进行排序
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

2. Set up the listener

源码剖析

/**
 * Sets the {@link ApplicationListener}s that will be applied to the SpringApplication
 * and registered with the {@link ApplicationContext}.
 * @param listeners the listeners to set
 */
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
	this.listeners = new ArrayList<>(listeners);
}

Inherit the ApplicationListener() interface

源码剖析

/**
 * Interface to be implemented by application event listeners.
 *
 * <p>Based on the standard {@code java.util.EventListener} interface
 * for the Observer design pattern.
 *
 * <p>As of Spring 3.0, an {@code ApplicationListener} can generically declare
 * the event type that it is interested in. When registered with a Spring
 * {@code ApplicationContext}, events will be filtered accordingly, with the
 * listener getting invoked for matching event objects only.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @param <E> the specific {@code ApplicationEvent} subclass to listen to
 * @see org.springframework.context.ApplicationEvent
 * @see org.springframework.context.event.ApplicationEventMulticaster
 * @see org.springframework.context.event.EventListener
 */
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

}

Summary :

The observer mode is used here. There is an observer and many observers. When the state of the observer changes, all observers must be notified to do some operations.

3. Infer the main application entry class

源码剖析

private Class<?> deduceMainApplicationClass() {
	try {
		// 构造一个异常类
		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
		for (StackTraceElement stackTraceElement : stackTrace) {
			// 通过main的栈帧推断出入口类的名字
			if ("main".equals(stackTraceElement.getMethodName())) {
				return Class.forName(stackTraceElement.getClassName());
			}
		}
	}
	catch (ClassNotFoundException ex) {
		// Swallow and continue
	}
	return null;
}

Three, run() method source code analysis

源码剖析

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
	// 记时器,统计应用启动的时间
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	// 初始化应用上下文和异常报告集合
	ConfigurableApplicationContext context = null;
	// SpringBootExceptionReporter 是异常处理器,启动的时候通过它把异常信息展示出来
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	 // 设置系统属性java.awt.headless的值,默认为true
	configureHeadlessProperty();
	// 监听器,SpringApplicationRunListeners实际上是一个集合
	SpringApplicationRunListeners listeners = getRunListeners(args);
	// 回调所有的获取SpringApplicationRunListener.starting()方法
	listeners.starting();
	try {
		// 初始化默认参数
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		// 准备 Spring 环境
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		// 创建环境完成后回调,配置bean
		configureIgnoreBeanInfo(environment);
		// 打印器,springboot启动的时候会打印springboot的标志以及对应的版本
		Banner printedBanner = printBanner(environment);
		// 创建Spring应用上下文,来决定创建web的ioc还是普通的ioc
		context = createApplicationContext();
		// 实例化异常报告器
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		//准备上下文环境
        // Spring上下文前置处理
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		// prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
		// Spring上下文刷新,表示刷新完成,进行后续的一些操作
		refreshContext(context);
        // Spring上下文后置处理
		afterRefresh(context, applicationArguments);
		// 停止计时器
		stopWatch.stop();
		// 输出日志记录的类名、时间信息
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		// 发布应用上下文启动完成事件
		listeners.started(context);
		// 执行所有 Runner 运行器
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		// 发布应用上下文就绪事件
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	// 返回应用上下文
	return context;
}

1. Start the timer

Start the timer to count the time the application has started

public void start() throws IllegalStateException {
		// 传入一个空字符串作为当前任务的名称
        this.start("");
    }

    public void start(String taskName) throws IllegalStateException {
        if (this.currentTaskName != null) {
        	// 如果当前任务名字不为空,抛出异常
            throw new IllegalStateException("Can't start StopWatch: it's already running");
        } else {
        	// 否则,记录当前任务的开始时间
            this.currentTaskName = taskName;
            this.startTimeNanos = System.nanoTime();
        }
    }
  • First, pass in an empty string as the name of the current task
  • Second, determine whether the current task name is empty, if it is empty, record the start time of the current application startup

2. Set the value of the system property

The default value of the system property is true, and the value of the system property comes from System.getProperty().

private void configureHeadlessProperty() {
		System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
				System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
	}

3. Listener

private SpringApplicationRunListeners getRunListeners(String[] args) {
		// 类加载对应的监听器
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		// 创建SpringApplicationRunListener实例
		return new SpringApplicationRunListeners(logger,
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
	}

4. Initialize the default parameters

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

5. Create Spring Environment

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 获取环境。如果存在就直接返回,否则先创建一个再返回
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置环境
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 准备监听器环境
    listeners.environmentPrepared(environment);
    // 将环境绑定到SpringApplication上面
    bindToSpringApplication(environment);
    // 如果不是web应用环境,将环境转换成StandardEnvironment
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    ConfigurationPropertySources.attach(environment);
    // 返回环境
    return environment;
}

Summary :

  • Get the environment. If it exists, return directly, otherwise create one and return
  • Configuration Environment
  • Prepare the listener environment
  • Bind the environment to SpringApplication
  • If it is not a web application environment, convert the environment to StandardEnvironment
  • Finally return to the environment

6. Printer

When springboot starts, it will print the springboot logo and the corresponding version

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);
	}

7. Create Spring Application Context

protected ConfigurableApplicationContext createApplicationContext() {
	// 首先进行判断有没有指定的实现类
	Class<?> contextClass = this.applicationContextClass;
	// 如果没有,则根据应用类型选择
	if (contextClass == null) {
		try {
			// 根据webApplicationType的类型去反射创建ConfigurableApplicationContext的具体实例
			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);
}

Summary :

  • First, judge whether there is a specified implementation class; if not, choose according to the application type;
  • According to the type of webApplicationType to reflect to create a specific instance of ConfigurableApplicationContext;
  • Finally, through reflection, the created object is obtained

For web applications, the context type is DEFAULT_WEB_CONTEXT_CLASS.

8. Instantiate the exception reporter

Use the getSpringFactoriesInstances() method to obtain the configured exception class name and instantiate all exception classes.

源码剖析

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));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

9. Spring context preprocessing

源码剖析

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	// 给IOC容器设置一些环境属性
	context.setEnvironment(environment);
	// 给IOC容器注册一些组件
	postProcessApplicationContext(context);
	// 调用初始化方法
	applyInitializers(context);
	// 监听器,触发contextPrepared 事件
	listeners.contextPrepared(context);
	// 记录启动过程中的日志
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
	// 添加特定的单例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");
	// 加载启动类,将启动类注入到容器中去
	load(context, sources.toArray(new Object[0]));
	// 触发contextLoaded 事件
	listeners.contextLoaded(context);
}

10. Spring context refresh

After the refresh is completed, some follow-up operations will be performed

源码剖析

private void refreshContext(ConfigurableApplicationContext context) {
	// 调用父类的refresh操作
	refresh(context);
	if (this.registerShutdownHook) {
		try {
			// 注册一个关闭容器时的钩子函数,在JVM关机的时候关闭这个上下文。
			context.registerShutdownHook();
		}
		catch (AccessControlException ex) {
			// Not allowed in some environments.
		}
	}
}

The registerShutdownHook() method is called

/**
 * Register a shutdown hook {@linkplain Thread#getName() named}
 * {@code SpringContextShutdownHook} with the JVM runtime, closing this
 * context on JVM shutdown unless it has already been closed at that time.
 * <p>Delegates to {@code doClose()} for the actual closing procedure.
 * @see Runtime#addShutdownHook
 * @see ConfigurableApplicationContext#SHUTDOWN_HOOK_THREAD_NAME
 * @see #close()
 * @see #doClose()
 */
@Override
public void registerShutdownHook() {
	if (this.shutdownHook == null) {
		// No shutdown hook registered yet.
		this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
			@Override
			public void run() {
				synchronized (startupShutdownMonitor) {
					// 调用doClose方法,进行容器销毁时的清理工作
					doClose();
				}
			}
		};
		Runtime.getRuntime().addShutdownHook(this.shutdownHook);
	}
}

11. Spring context post processing

It is called after the Spring container refreshes the context, and the registered Runners are called in turn.

/**
 * Called after the context has been refreshed.
 * @param context the application context
 * @param args the application arguments
 */
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

private void callRunners(ApplicationContext context, ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();
	runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
	runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
	AnnotationAwareOrderComparator.sort(runners);
	// CommandLineRunner、ApplicationRunner 这两个接口,是在容器启动成功后的最后一步进行回调
	for (Object runner : new LinkedHashSet<>(runners)) {
		if (runner instanceof ApplicationRunner) {
			callRunner((ApplicationRunner) runner, args);
		}
		if (runner instanceof CommandLineRunner) {
			callRunner((CommandLineRunner) runner, args);
		}
	}
}

The two interfaces, CommandLineRunner and ApplicationRunner, are callbacks in the last step after the container starts successfully.

12. Stop the timer

Do the timing monitor to stop the operation, and count some task execution information

public void stop() throws IllegalStateException {
    if (this.currentTaskName == null) {
        throw new IllegalStateException("Can't stop StopWatch: it's not running");
    } else {
        long lastTime = System.nanoTime() - this.startTimeNanos;
        this.totalTimeNanos += lastTime;
        this.lastTaskInfo = new StopWatch.TaskInfo(this.currentTaskName, lastTime);
        if (this.keepTaskList) {
            this.taskList.add(this.lastTaskInfo);
        }

        ++this.taskCount;
        this.currentTaskName = null;
    }
}

13. Publish Spring context startup completion event

void started(ConfigurableApplicationContext context) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.started(context);
	}
}

14. Execute all Runner runners

private void callRunners(ApplicationContext context, ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();
	runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
	runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
	AnnotationAwareOrderComparator.sort(runners);
	for (Object runner : new LinkedHashSet<>(runners)) {
		if (runner instanceof ApplicationRunner) {
			callRunner((ApplicationRunner) runner, args);
		}
		if (runner instanceof CommandLineRunner) {
			callRunner((CommandLineRunner) runner, args);
		}
	}
}

15. Publish Spring Context Ready Event

void running(ConfigurableApplicationContext context) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.running(context);
	}
}

The method to trigger the running event of all SpringApplicationRunListener listeners.

On the basis of the original blogger, I have added some important steps that I think are limited, so do not spray. The emphasis is on sharing mutual progress!

Guess you like

Origin blog.csdn.net/zw764987243/article/details/112787360