springboot启动
- 先从主类的run方法开始,事例代码如下
public static void main(String[] args) {
SpringApplication.run(PromoteAdminApplication.class, args);
logger.info("PromoteAdminApplication is success!");
}
- 跟进run方法最终可以看到
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
我们根据注释看到,代码是在创建一个spring的应用。继续跟进new SpringApplication(primarySources),run方法后面在看,先看在new的时候程序都做了什么。
/**
* 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) {
//设置资源加载类ResourceLoader,传的都是null
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//设置主类,就是我们最开始启动类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//设置应用类型 根据加载的目标类是否存在来判断
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// **此处是spring自己的spi机制,在META-INF/spring.factories加载class,这也是自动配置与去xml的基础,这里加载的**
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 通过spi机制加载ApplicationListener类型的class
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 设置运行的主类,并保存在spring应用类的属性内
this.mainApplicationClass = deduceMainApplicationClass();
}
可以看到 SpringApplication创建时,做了很多初始化的工作
我们先看下Spring如何使用spi机制
- 加载对应属性文件并初始化加载的class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//实例化对应clas
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
- 如何加载
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//指定加载路径 FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// 创建map 装载属性文件内容的一个容器()
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//加载URL下的文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
// 根据线程的类加载器为key,缓存加载的属性文件的内容
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
这段代码目的就是加载好spring.factories内的class名称,
但是此时springboot在加载的时候 加载的是ApplicationContextInitializer类型的class名称
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
加载完成后 springboot就开始实例化这些class
- 如何实例化
主要是利用反射初始化对象
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
//根据名字反射获取class对象
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;
}
还记得最开始的SpringApplication 构造函数初始化的步骤吗
setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));
这里面可以看下里面的源码
- 这里面可以看到 将ApplicationContextInitializer实例存到list内,注入到spring的对象属性内,作用就是后面spring启动过程中使用此方法进行初始化配置。
/**
* 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<>();
this.initializers.addAll(initializers);
}
- 同理先存储ApplicationListener类型class实例,后面使用
/**
* 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<>();
this.listeners.addAll(listeners);
}
这里看到此时spring已经把需要的监听加载完成,只要等待对应事件的发布就ok了
这里只是SpringApplication的构造过程而已,然后调用run方法。这里才是真正的启动spring应用的方法。
- 主要步骤如下 1. 配置属性 > 2. 获取监听器,发布应用开始启动事件 > 3. 初始化输入参数 > 4. 配置环境,输出 banner > 5. 创建上下文 > 6. 预处理上下文 > 7. 刷新上下文 > 8. 上下文已刷新启动完成 发布启动完成事件 > 9. 调用CommandLineRunners与ApplicationRunner的run方法 > 10. 发布应用上下文就绪事件
refreshContext方法就是spring容器的主要方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//1. 配置属性 主要设置 java.awt.headless属性,目的就是在缺少显示屏时也可以启动
configureHeadlessProperty();
// 2. 获取监听器,发布应用开始启动事件
//获取SpringApplicationRunListeners,内部只有一个EventPublishingRunListener 这里会将META-INF/spring.factories内的信息全部加载到map内
SpringApplicationRunListeners listeners = getRunListeners(args);
//启动监听
listeners.starting();
try {
//3. 初始化输入参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//3. 初始化输入参数
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
4. 配置环境,输出 banner
Banner printedBanner = printBanner(environment);
//5. 创建上下文
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 6. 预处理上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//7. 刷新上下文
refreshContext(context);
//刷新context后执行,空方法
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 8.上下文已刷新启动完成 发布启动完成事件
listeners.started(context);
//9. 调用CommandLineRunners与ApplicationRunner的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//10.发布应用上下文就绪事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}