既然看到这篇文章了,那么默认读者已经很熟悉SpringBoot的使用的。
第一步,启动一个SpringBoot应用:
@ComponentScan(basePackages = {""})
@MapperScan("")
@SpringBootApplication
public class StartApp {
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(StartApp.class);
// 修改Banner的模式为OFF
builder.bannerMode(Banner.Mode.LOG).run(args);
//加载系统配置项
}
}
或者呢,可以这样启动:
@ComponentScan(basePackages = {""})
@MapperScan("")
@SpringBootApplication
public class StartApp {
public static void main(String[] args) {
SpringApplication sa = new SpringApplication(StartApp.class);
sa.run();
}
}
两者是等价的,但是推荐使用第一种。为什么呢?因为SpringApplicationBuilder对SpringApplication做了一些封装,我们可以按需要设置更多定制化配置项。
第二步,SpringApplicationBuilder的实例化
public SpringApplicationBuilder(Class<?>... sources) {
this.application = createSpringApplication(sources);
}
protected SpringApplication createSpringApplication(Class<?>... sources) {
return new SpringApplication(sources);
}
以上就是在实例化一个SpringApplicationBuilder类时它为我们做的事情。可以发现,它间接实例化了一个SpringApplication对象,这就是它们为什么等价的原因。好了,这些当然不是这篇文章的主要任务。我们接着往下看。
第三步,SpringApplication对象的实例化
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//resourceLoader的设置
this.resourceLoader = resourceLoader;
//断言保证primarySources不能为空,也就是实例化SpringApplication时的参数不能为空
Assert.notNull(primarySources, "PrimarySources must not be null");
//把primarySources数组转换为set集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判断webApplicationType,也就是当前Springboot应用类型
this.webApplicationType = deduceWebApplicationType();
//收集ApplicationContextInitializer事件类
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//收集ApplicationListener事件类
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
在上述方法中,忽略一眼就能看明白的步骤,我们可以看到主要做了一些搜集ApplicationContextInitializer和ApplicationListener类的工作。根据getSpringFactoriesInstances()方法的名称,我们简单判断是一个从Spring容器中获取指定类型对象的方法。那么,具体看看它的实现:
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 = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
//抽象出配置文件中所有指定类型的Factory的名称集合
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//创建Spring Factory类的实例集合
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
非常有意思的是SpringFactoriesLoader.loadFactoryNames(type, classLoader))方法这一步做的事情,它会把META-INF/spring.factories文件中的所有Factory的名称都扫描出来并放到集合中,代码如下:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//如果当前Springboot所在线程的classLoader 不为空,就从classLoader中获取META-INF/spring.factories文件;
//否则,从系统盘中获取(针对不同运行环境)
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)) ;
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//把
UrlResource resource = new UrlResource(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);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
}
}
到这里,一些属性设置相关的操作就完成了。当然,SpringApplicationBuilder和SpringApplication都支持很多个性化配置。我们可以自己去设置相关内容。
第四步,SpringApplicationBuilder的启动run()方法。
public ConfigurableApplicationContext run(String... args) {
//如果已经启动就直接返回当前context并停止后续操作
if (this.running.get()) {
// If already created we just return the existing context
return this.context;
}
//如果父容器不为空,那么标记当前应用为子容器并启动父容器(Maven多模块开发中会用到)
configureAsChildIfNecessary(args);
//如果当前应用没有启动就启动它
if (this.running.compareAndSet(false, true)) {
synchronized (this.running) {
// If not already running copy the sources over and then run.
this.context = build().run(args);
}
}
return this.context;
}
可以看到,它调用了一个run()方法,由SpringApplication来实现的:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//搜索得到所有的启动时ApplicationListener事件监听类
SpringApplicationRunListeners listeners = getRunListeners(args);
//启动所有的启动时ApplicationListener事件监听类
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
//打印Banner条
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
repareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
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;
}
需要重点关注refreshContext(context)方法,它的实现如下:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
好了,终于要放大招了。看到refresh(context)方法没?它的实现在AbstractApplicationContext类中。这个类不用多说,你懂得^>^。