前言

    上一章讲述了快速构建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 boot 项目启动完成之后 执行_spring boot

可以看出不过是一些spring的封装,本质上就是传统spring Web MVC,将json和autoconfigure、log、JSR303标准,并内置tomcat嵌入式版。

1.2 spring-boot-starter-test

spring boot 项目启动完成之后 执行_spring boot_02

将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);看意思是判断已定义且可加载

spring boot 项目启动完成之后 执行_加载_03

 进一步查看原方法

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);
		}
	}

一次性读取,然后缓存起来,然后每次直接取

spring boot 项目启动完成之后 执行_加载_04

下一步初始化创建实例了

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 项目启动完成之后 执行_spring boot_05

spring boot 项目启动完成之后 执行_初始化_06

 

总结

    自此,Spring boot的初始化的过程就结束了,主要是初始化运行中的基本属性。注意是几步

    1. 初始化默认属性

    2. 初始化初始类,就是我们传入的类

    3. 初始化应用类型

    4. 初始化ApplicationContextInitializer上下文初始器

    5. 初始化ApplicationListener

    6. 初始化main函数的类