1、初始化的数据处理
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
我们传过去的类和jvm启动参数最终以这个形式运行,做了两步,新建spring应用对象,然后run
2 新建spring应用对象
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
// 这个类里定义了一个环境类数组,是两个字符串,"javax.servlet.Servlet",
// 和"org.springframework.web.context.ConfigurableWebApplicationContext"
// 这个deduceWebEnvironment的作用就是判断字符串代表的类能否通过classLoader加载
// 返回布尔值
this.webEnvironment = deduceWebEnvironment();
// 将boot下spring.factories文件中的ApplicationContextInitializer接口的实现类实例化放到成员属性 initializers 中
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 将boot下同上文件夹中监听器实现类放到监听器成员属性中
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 返回启动类
this.mainApplicationClass = deduceMainApplicationClass();
}
2.1 几个有趣的地方
2.1.1 断言异常
Assert.notNull(name, "Name must not be null");
public static void notNull(Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
这段代码的作用是判空,如果是空则抛出一个参数不合法的异常,message是第二参数
常规都是用stringutil.isnotNull,这里用了异常觉得很有意思
在effective java中提到,不要滥用异常,异常的使用是非正常流程的情况下,也就是说遇到异常通常就是业务不能继续走下去了。
那么这里定义的异常断言,应该也是合情合理的,平时我们开发用了stringUtil去判空,如果为空可以处理的话使用没有问题,如果已经不能处理需要抛出异常给客户端的话,不妨使用这种断言异常。
2.1.2 map初始化容量
private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new IdentityHashMap<Class<?>, Class<?>>(8);
private static final Map<String, Class<?>> primitiveTypeNameMap = new HashMap<String, Class<?>>(32);
private static final Map<String, Class<?>> commonClassCache = new HashMap<String, Class<?>>(32);
在翻看springboot源码过程中看到了他定义的hashmap都初始化了容量,那么就在想这个是不是对性能提升有优化,于是百度搜了下,确实有,详情参考 https://blog.csdn.net/a397525088/article/details/81112074
2.1.3 spring.factories加载和类加载机制
在初始化的第二句可以看到是
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));
一个初始化的内容,代码如下
private <T> Collection<? extends 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<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 获取工厂实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 排个序 返回
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这里要说的是获取工厂名,这段代码所在的jar包是spring-boot-1.5.14,调用的方法在spring-core-4.3.18里,是读取
meta-inf下名为spring.factorie的配置文件,一开始我以为是找我自己项目的metf-inf文件,没找到,后来就找spring-core里meta-inf中的文件,也没有这个叫factories的文件,最后在spring-boot里才找到,他调用的方法是
classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)
classLoader是从boot传到core里的加载器,所以最后确定ClassLoader里加载资源文件的根目录是他这个加载器所处的项目里..
据说这个是SPI机制,我就不去考究了继续撸源码。。
贴上配置文件里的数据 1.5.14版本的 应该都是有东西的类
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
2.1.4 堆栈
debug也一两年了第一次知道这里可以看调用顺序还是昨天同事截图看了一眼才发现的
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
最后一行方法的详细代码就是上面这一段
new RuntimeException().getStackTrace();
这个方法可以获得当前调用的堆栈,一直遍历直到找到方法名为main的类,实例化返回,这里不知道为什么不将这个变量名直接传递过来而要使用这种遍历的方式。比如我这里返回的就是
com.example.demo.SmartPlatApplication
一直到这里,初始化的工作就完成了,后面是run的代码。
总结一下初始化的工作主要做的就是加载servlet类和config类,将启动类和监听器类加载到成员变量中,然后run。
--后面在分析,先把run的代码贴上来
public ConfigurableApplicationContext run(String... args) {
// 计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 初始化
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
// 实例化监听器,在上面讲的的配置文件里
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}