之前的经历让我发现了,其实很多东西还需要学习。
不仅仅是我所谓的知识体系。
接下来我会慢慢的重塑自己的知识体系,并一边弥补自己的面试缺憾。
由于是解读SpringBoot,为了去除其他的干扰,从Spring的官网生成(https://start.spring.io/)了一个最简单的SpringBoot项目。
我们来看看*Application.java文件的代码:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
从上到下解读,首先是包名目录,然后导入的指令。
从line6解读,这里是springboot专有的注解,我们点进去看看。
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.repository.Repository;
@Target(ElementType.TYPE) // Java的元注解,说明这是一个接口或类或枚举
@Retention(RetentionPolicy.RUNTIME) // 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Documented // Documented注解表明这个注释是由 javadoc记录的,在默认情况下也有类似的记录工具。如果一个类型声明被注释了文档化,它的注释成为公共API的一部分
@Inherited // 使用此注解声明出来的自定义注解,在使用此自定义注解时,如果注解在类上面时,自动继承此注解,否则的子会话,子类不会继承此注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication { // @interface 并不是说明这是一个接口
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
为了节省篇幅,
@Target(ElementType.TYPE)--声明类型
@Retention(RetentionPolicy.RUNTIME)--注解进入jvm仍然存在
@Documented--javadoc,公共api说明
@Inherited--让使用这个注解的类继承这个注解的所有注解(敲黑板)
的使用已经在代码中注释。
接下来重点讲解一下这个类最主要的三个注解:
@ComponentScan是自动扫描并加载符合条件的组件或者bean,最终将这些bean放入IOC容器中,我们可以通过basePackages属性来定义扫描范围,如果不指定默认是从注解所在类的package开始扫描。
excludeFilters--排除过滤规则
@Filter(type = FilterType.CUSTOM--按照自定义来进行过滤和筛选,后面的classes就是自定义的匹配方法
下面的方法定义
@AliasFor标识别名的意思。
exclude —— 根据Class对象来排除特定的类,不加入到Spring容器中,传入Class数组。 excludeName —— 根据类名来排除特定的类,不加入到Spring容器中,传入参数是类的全限定名数组。 scanBasePackages —— 指定自动扫描的包,参数是字符串数组。 scanBasePackageClasses —— 指定自动扫描的包,参数是Class对象数组。
proxyBeanMethods默认是true,如果为false配置类就不会被代理,不代理可以减少springboot的启动时间。
回到第一段代码line9,点进去看源码
// SpringApplication.run(DemoApplication.class, args); 调用下面的代码
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(Class数组).run(输入字符串),其中class数组只有一个内容DemoApplication.class
我们先看创建方法,解释完再看run方法就会相对轻松一些
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 将DemoApplication.class赋值给resourceLoader
this.resourceLoader = resourceLoader;
// 用断言判断Class是否为空,这里...是多参数兼容,用数组接收
Assert.notNull(primarySources, "PrimarySources must not be null");
// 将class数组转换成List然后全部放进LinkedHashSet中,初始化并去重
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 查看web应用类型,如果能够实例化DispatcherHandler,则返回REACTIVE,如果无法获得相应的类加载器,则返回NONE,否则返回SERVLET
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置应用上线文初始化器,从"META-INF/spring.factories"读取ApplicationContextInitializer类的实例名称集合并去重,并进行set去重。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器,从"META-INF/spring.factories"读取ApplicationListener类的实例名称集合并去重,并进行set去重。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 获取当前调用线程的栈,遍历获取栈中main方法所在类名,通过类名getClass并赋值给mainApplicationClass
this.mainApplicationClass = deduceMainApplicationClass();
}
从上面不难看出,其实只是做了一些初始化的操作,对变量的赋值和启动初始化器和监听器。
接下来进入run方法
// 创建spring的计时器
StopWatch stopWatch = new StopWatch();
// 调用启动计时器,如果已经启动了就会抛异常IllegalStateException,否则启动,并记录当前系统时间纳秒
stopWatch.start();
// 初始化应用上下文
ConfigurableApplicationContext context = null;
// 初始化异常报告集合
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 设置系统属性java.awt.headless,默认为"true",用于运行headless服务器,进行简单的图像处理
configureHeadlessProperty();
// 创建所有spring运行监听器并发布应用启动事件,即获取SpringApplicationRunListener类型实例(EventPublishingRunListener),放入SpringApplicationRunListeners中,然后返回
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动返回的所有listener
listeners.starting();
try {
// 初始化默认应用参数类
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 根据运行监听器和参数来准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 如果系统属性里没有spring.beaninfo.ignore,就根据环境属性获取,默认是"true" -- 打开忽略bean
configureIgnoreBeanInfo(environment);
// 获取当前输出类型,off/console/log
Banner printedBanner = printBanner(environment);
// 创建应用上下文(一个容器)
context = createApplicationContext();
// 获取异常报告器,用于报告启动异常
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备应用上下文,该步骤包含一个非常关键的操作,将启动类注入容器,为后续开启自动化提供基础
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新应用上下文
refreshContext(context);
// 刷新后的操作,默认为空
afterRefresh(context, applicationArguments);
// 计时器停止计时
stopWatch.stop();
if (this.logStartupInfo) { // this.logStartupInfo默认为true
// 打印启动日志
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 应用上下文启动监听事件
listeners.started(context);
// 从上下文中获取实现了ApplicationRunner或CommandLineRunner接口的任务并调度--可以用来执行业务初始化,只要实现这两个其中一个接口
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;
在这个过程中,有一些源码一直追溯到最底层,如果在这里展开篇幅过长。这里就不作详细介绍,不过一路点下去看底层的代码还是很有趣的,能发现spring的实现功能大多是用的反射。
其中涉及到的一个配置文件读取的路径是spring-boot-autoconfigure-2.2.2.RELEASE.jar中META-INF文件下的spring.factories。
附上启动流程图