spring boot程序启动入口是:
@SpringBootApplication
public class ApplicationServer {
public static void main(String[] args) {
SpringApplication.run(ApplicationServer.class, args);
}
}
看下SpringApplication这个类的说明:
Class that can be used to bootstrap and launch a Spring application from a Java main
method.
该类用于启动和运行spring应用程序。
通常情况下运行spring应用程序可以分为以下步骤:
- 创建合适的ApplicationContext实例
- 注册一个CommandLinePropertySource,暴露命令行参数作为spring属性参数
- 刷新application context,加载所有的单例bean
- 触发CommandLineRunner bean
这种方式运行spring应用比较少,大多数情况会在main方法里直接调用以启动应用程序:
* @Configuration
* @EnableAutoConfiguration
* public class MyApplication {
*
* public static void main(String[] args) {
* SpringApplication.run(MyApplication.class, args);
* }
* }
有时候启动的时候希望进行一些高级配置,可以这么做:
SpringApplication application = new SpringApplication(MyApplication.class);
//... config your settings here
application.run(args)
可以看下可以进行哪些设置:
比如进行spring boot启动banner设置,webApplicationType设置等等。
SpringApplication可以从不同的数据源读取bean,如:
- 由XmlBeanDefinitionReader加载,bean配置在XML文件中。
- 由GroovyBeanDefinitionReader加载,bean配置在script脚本中。
- 由ClassPathBeanDefinitionScanner加载,扫描包的方式。
- 根据类全限定名由AnnotatedBeanDefinitionReader加载。
大致分析完SpringApplication这个类的功能,继续往下走:
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
顺着调用栈,再往后:
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
先看一下这个构造方法new SpringApplication(primarySources)做了哪些事情:
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
创建SpringApplication实例,接着调用的是另外一个构造方法(我们可以看到spring很多地方用了可变参数...) :
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
这里的primarySources就是主类,webApplicationType.deuceFromClasspath()是推测web应用的类型,点进去我们可以看到
WebApplicationType是枚举类,列举了web应用类型分类:
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;
SERVLET是传统的web服务,如果是这种类型则会启动一个内嵌的servlet web服务容器,默认是Tomcat,可以修改。
REACTIVE是一种异步非阻塞web框架,拥有高并发、高性能的特点,是构建中大型应用的首选。
NONE就不用介绍了,非web类型服务。
接着看方法deduceFromClasspath方法:
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
ClassUtils.isPresent方法是判断类存在且可被加载,如果类或者其依赖不存在或者不能加载则返回false;
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
try {
forName(className, classLoader);
return true;
}
catch (IllegalAccessError err) {
throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
className + "]: " + err.getMessage(), err);
}
catch (Throwable ex) {
// Typically ClassNotFoundException or NoClassDefFoundError...
return false;
}
}
forName方法是加载某个类,但是使用的不是Class.forName,对Class.forName进行了扩展或者说增强,我们知道Class.forName加载的类名为string类型,而此处的forName则可以加载原生的数据类型。
public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
Assert.notNull(name, "Name must not be null");
Class<?> clazz = resolvePrimitiveClassName(name);
if (clazz == null) {
clazz = commonClassCache.get(name);
}
if (clazz != null) {
return clazz;
}
// "java.lang.String[]" style arrays
if (name.endsWith(ARRAY_SUFFIX)) {
String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
Class<?> elementClass = forName(elementClassName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
// "[Ljava.lang.String;" style arrays [L一维数组
if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
Class<?> elementClass = forName(elementName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
// "[[I" or "[[Ljava.lang.String;" style arrays [[二维数组
if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
Class<?> elementClass = forName(elementName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
ClassLoader clToUse = classLoader;
if (clToUse == null) {
clToUse = getDefaultClassLoader();
}
try {
return Class.forName(name, false, clToUse);// 调用java.lang包
}
catch (ClassNotFoundException ex) {
int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
if (lastDotIndex != -1) {
String innerClassName =
name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
try {
return Class.forName(innerClassName, false, clToUse);
}
catch (ClassNotFoundException ex2) {
// Swallow - let original exception get through
}
}
throw ex;
}
}
其中调用resolvePrimitiveClassName方法:
public static Class<?> resolvePrimitiveClassName(@Nullable String name) {
Class<?> result = null;
// Most class names will be quite long, considering that they
// SHOULD sit in a package, so a length check is worthwhile.
if (name != null && name.length() <= 8) {
// Could be a primitive - likely.
result = primitiveTypeNameMap.get(name);
}
return result;
}
primitiveTypeNameMap在静态代码块中被初始化。
for (Class<?> primitiveType : primitiveTypes) {
primitiveTypeNameMap.put(primitiveType.getName(), primitiveType);
}
存储的是基础数据类型的类名和对应类类型。详细可查看static代码块。
实际上对于我们给定主类的程序,Class<?> clazz = resolvePrimitiveClassName(name)得到的clazz肯定是null,clazz = commonClassCache.get(name)返回的也是null,commonClassCache是在静态代码块里调用registerCommonClasses方法里赋值的。最终会调用Class.forName(name, false, clToUse)加载类。
回到deduceFromClasspath方法,在判断完相应的类是否存在后,返回具体的web应用类型。
回到SpringApplication构造方法,接下来的两步是设置spring上下文初始化器和监听器。
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
这两步获取工厂实例的方法是一样的,通过读取spirng core包下的META/spring.factories文件来实例化不同工厂的实例。
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
从上述源码可以学习到:
- 如何加载工厂实例
- spring自定义了MultiValueMap数据结构存储多value值以满足自身功能需要
往后是deduceMainApplicationClass()方法推测main方法所在主类,主要是借助StackTraceElement这个类来测。