spring boot的启动流程图
springboot的流程启动主要为三个部分
- SpringApplication的初始化,配置基础的环境变量,构造器,监听器等。
- 流程启动,主要为启动流程的监听,加载配置环境,创建上下文
- 自动化配置
springboot的主启动类入口
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@MapperScan(value = "cn.dingdm.website.mapper")
@EnableCaching
public class WebsiteApplication {
public static void main(String[] args) {
SpringApplication.run(WebsiteApplication.class, args);
}
}
SpringBootApplication注解详解
@Target({ElementType.TYPE}) //注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented // 表明这个注解应该被javadoc记录
@Inherited // 子类可以继承该注解
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
@ComponentScan( // 扫描路径设置
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
ElementType枚举类
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE
}
生命周期枚举类
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
注解 | 父类 | 作用 |
---|---|---|
SpringBootConfiguration | Configuration | 标注当前类是Java config配置类,会被扫描并加载到ioc容器 |
ComponentScan | 扫描默认包或指定包下面符合条件的组件并加载 | |
EnableAutoConfiguration | 从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfig.EnableAutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的java config形式的ioc容器配置类,汇总并加载到ioc容器 | |
@AutoConfigurationPackages | 注册当前主程序类的同级以及子级的包中的符合条件的Bean的定义 | |
@Import(AutoConfigurationImportSelector.class) | 扫描各个组件jar META-INIF目录下的spring.dactories文件,将下面的包名.类名中的工厂类全部加载到IOC容器中;将所有符合条件的bean的定义加载到ioc容器中 |
springboot启动
执行主函数main方法
创建SpringApplication对象对象,并运行SpringApplication对象的run方法
加载接口
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = this.deduceWebApplicationType();
// 扫描当前路径下META-INF/spring.factories文件的,加载ApplicationContextInitializer接口实例
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 扫描当前路径下META-INF/spring.factories文件的,加载ApplicationListener接口实例
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用
ApplicationListener 当springboot启动时事件change后都会触发
这两个接口均可自己进行定义
实现接口后自己配置自己的实现类
org.springframework.context.ApplicationContextInitializer=\
org.admin.starter.test.listener.StarterApplicationContextInitializer
org.springframework.context.ApplicationListener=\
org.admin.starter.test.listener.StarterApplicationListener
实现run方法
public ConfigurableApplicationContext run(String... args) {
// 计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();
// 设置环境变量
configureHeadlessProperty();
// 获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 参数args封装成DefaultApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 把环境跟spring上下文绑定好,并且执行environmentPrepared()方法
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 判断一些环境的值,设置一些环境的值
configureIgnoreBeanInfo(environment);
// 打印banner
Banner printedBanner = printBanner(environment);
// 根据项目类型创建上下文
context = createApplicationContext();
// 获取异常报告事件监听
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备上下文,执行完成后调用contextPrepared()方法,contextLoaded()方法
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// spring启动的代码,这里就回去里面就回去扫描并且初始化单实列bean了
// 这个refreshContext()加载了bean,启动了内置web容器
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 执行ApplicationRunListeners中的started()方法
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
listeners.running(context);
return context;
}
根据项目类型创建上下文,并且注入几个核心组件类。
refreshContext(context)方法启动spring的代码加载了bean,还启动了内置web容器
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
onRefresh钩子方法
钩子方法,它会钩到它子类重写onRefresh()方法。所以去看子类里面的onRefresh()
内置容器
容器选择
tomcat容器
springboot的自动化配置
该配置模块的主要使用到了SpringFactoriesLoader,即Spring工厂加载器,该对象提供了loadFactoryNames方法,入参为factoryClass和classLoader,即需要传入上图中的工厂类名称和对应的类加载器,方法会根据指定的classLoader,加载该类加器搜索路径下的指定文件,即spring.factories文件,传入的工厂类为接口,而文件中对应的类则是接口的实现类,或最终作为实现类,所以文件中一般为如下图这种一对多的类名集合,获取到这些实现类的类名后,loadFactoryNames方法返回类名集合,方法调用方得到这些集合后,再通过反射获取这些类的类对象、构造方法,最终生成实例。
EnableAutoConfiguration最终实现了ImportSelector(选择器)和BeanClassLoaderAware(bean类加载器中间件),重点关注一下AutoConfigurationImportSelector的selectImports方法
该方法在springboot启动流程——bean实例化前被执行,返回要实例化的类信息列表。我们知道,如果获取到类信息,spring自然可以通过类加载器将类加载到jvm中,现在我们已经通过spring-boot的starter依赖方式依赖了我们需要的组件,那么这些组建的类信息在select方法中也是可以被获取到的
方法中的getCandidateConfigurations方法,通过方法注释了解到,其返回一个自动配置类的类名列表,方法调用了loadFactoryNames方法
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
自动配置器会根据传入的factoryClass.getName()到项目系统路径下所有的spring.factories文件中找到相应的key,从而加载里面的类
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry, ?> entry = (Entry)var6.next();
List factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
result.addAll((String)entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var9) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
}
}
}
自动配置
@Configuration
@ConditionalOnClass({EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class})
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"auto"},
havingValue = "true",
matchIfMissing = true
)
public class AopAutoConfiguration {
public AopAutoConfiguration() {
}
@Configuration
@EnableAspectJAutoProxy(
proxyTargetClass = true
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"proxy-target-class"},
havingValue = "true",
matchIfMissing = true
)
public static class CglibAutoProxyConfiguration {
public CglibAutoProxyConfiguration() {
}
}
@Configuration
@EnableAspectJAutoProxy(
proxyTargetClass = false
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"proxy-target-class"},
havingValue = "false",
matchIfMissing = false
)
public static class JdkDynamicAutoProxyConfiguration {
public JdkDynamicAutoProxyConfiguration() {
}
}
}
@Configuration,是一个通过注解标注的springBean
@ConditionalOnClass({EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class})这个注解的意思是:EnableAspectJAutoProxy.class, Aspect.class,Advice.class,AnnotatedElement.class这几个类时才解析AopAutoConfiguration配置类,否则不解析这一个配置类
因为maven依赖的传递性,我们只要依赖starter就可以依赖到所有需要自动配置的类,实现开箱即用的功能。也体现出Springboot简化了Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发。