前言
上一章讲述了快速构建spring boot项目,这一章讲述spring boot是什么。
1. spring boot stater
查看快速构建的项目pom文件
<!-- 依赖关系 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 打包插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
下面来看spring-boot-starter-web和spring-boot-starter-test是什么东西
1.1 spring-boot-starter-web
可以看出不过是一些spring的封装,本质上就是传统spring Web MVC,将json和autoconfigure、log、JSR303标准,并内置tomcat嵌入式版。
1.2 spring-boot-starter-test
将junit与spring test与json打包一体化
2. spring boot启动过程分析new SpringApplication(primarySources)
跟踪main方法
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
进入SpringApplication
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
new 一个SpringApplication对象,执行args初始化环境参数,也就是说我们可以直接写代码这样运行。
@SpringBootApplication
public class SourceMain {
public static void main(String[] args) {
new SpringApplication(SourceMain.class).run(args);
}
}
2.1 构建容器SpringApplication
new SpringApplication的原理
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
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();
}
核心是初始化,Spring boot的初始化bean,下面分析。
2.2 加载默认属性
在执行构造函数之前加载一些默认初始属性,很多属性现在其实不知道干什么的,只有在启动过程,结合执行的能力才知道意义。
//暂时不知道用来干啥的
private Set<String> sources = new LinkedHashSet<>();
//console打印banner
private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
//看名称是日志记录启动信息,难道设置为false,启动没有日志?
private boolean logStartupInfo = true;
private boolean addCommandLineProperties = true;
private boolean addConversionService = true;
private boolean headless = true;
//这个很明显
private boolean registerShutdownHook = true;
private Set<String> additionalProfiles = new HashSet<>();
private boolean isCustomEnvironment = false;
private boolean lazyInitialization = false;
然后初始化构造函数
2.3 SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)
this(null, primarySources);
所以这里resourceLoader为null
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
//断言,说明初始资源不能null
Assert.notNull(primarySources, "PrimarySources must not be null");
//这里可以看出primarySources,加入了我们的main class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//web应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
2.3.1 定义应用的类型
WebApplicationType.deduceFromClasspath();
public enum WebApplicationType {
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
//不是web应用,不会运行嵌入式web server
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
//servlet 的web应用
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
//reactive web application响应式,比如Spring的webflex
REACTIVE;
跟踪可以看出这是一个枚举静态方法,spring boot默认类型是SERVLET,即servlet。
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
static WebApplicationType deduceFromClasspath() {
//判断有webflex的类,没有servlet的类,没有jersey的类
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
//没有servlet相关的类
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
//servlet
return WebApplicationType.SERVLET;
}
有一个关键的方法
ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null);看意思是判断已定义且可加载
进一步查看原方法
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(className, classLoader);
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
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;
//这里保证classloader不为null,难怪前面不赋值
if (clToUse == null) {
clToUse = getDefaultClassLoader();
}
try {
//核心在这里,反射获取class,看看class是否存在,并可加载
return Class.forName(name, false, clToUse);
}
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;
}
}
2.3.2 setInitializers()设置需要初始上下文的接口的实现
这个非常关键,这些接口涉及初始化上下文用的。第一步是拿到这些实现类,这里就有Spring boot非常关键的SpringFactories
getSpringFactoriesInstances(ApplicationContextInitializer.class)
这个方法spring boot使用非常频繁,以至于我们自己写starter的时候也会使用这种方式读取,重点说明
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 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;
}
getClassloader,这里说一下SpringBoot的区别
public ClassLoader getClassLoader() {
if (this.resourceLoader != null) {
return this.resourceLoader.getClassLoader();
}
return ClassUtils.getDefaultClassLoader();
}
先通过前面的resourceLoader获取,由于前面默认传null;走了Spring的获取方式,这本来没什么;但是对于热替换的应用很容易造成classloader的切换问题;对大部分应用不存在问题。
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
//线程拿
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
//当前类获取
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
//去系统拿
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
读取文件,获取类loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
这里类型是前面传入的ApplicationContextInitializer
关键是加载的过程
loadSpringFactories(classLoader)
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//缓存获取,提高效率,这里要注意MultiValueMap,Spring自己写的
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//读取文件META-INF/spring.factories,这些文件就是Spring boot的默认规范,我们也可以自己增加,按照Spring boot的规则即可生效
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文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
//读取的文件内容设置进多vlue的map中
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
//放入缓存,这个在以后的setApplicationListener或者其他用途时,就可以提速
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
一次性读取,然后缓存起来,然后每次直接取
下一步初始化创建实例了
createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names)
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<?> 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;
}
反射常规操作,没什么要说的,至此,SpringFactories就结束了。
2.3.3 setListeners
同理上一步,就不多介绍了
2.3.4 deduceMainApplicationClass
字面意思:推断主应用类,源码也可以看出,栈里看main方法的类,这么说来我们传入的初始的class可以不是main方法的类哦
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;
}
笔者试了,还真可以,😋
总结
自此,Spring boot的初始化的过程就结束了,主要是初始化运行中的基本属性。注意是几步
1. 初始化默认属性
2. 初始化初始类,就是我们传入的类
3. 初始化应用类型
4. 初始化ApplicationContextInitializer上下文初始器
5. 初始化ApplicationListener
6. 初始化main函数的类