Springboot最热点面试题
springboot启动流程
启动类上注解:@SpringBootApplication
@SpringBootConfiguration
根据Javadoc可知,该注解作用就是将当前的类作为一个JavaConfig,然后触发注解@EnableAutoConfiguration和@ComponentScan的处理,本质上与@Configuration注解没有区别
@EnableAutoConfiguration
@EnableAutoConfiguration:实现自动装配的核心注解
- @AutoConfigurationPackage
- 注册当前启动类的根 package
- 注册 org.springframework.boot.autoconfigure.AutoConfigurationPackages 的 BeanDefinition
- @Import(AutoConfigurationImportSelector.class)
- 自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector(加载自动装配类)类
- AutoConfigurationImportSelector 类实现了 ImportSelector接口
- 实现了这个接口中的 selectImports方法
- 方法实现 重要的getAutoConfigurationEntry()方法
- 判断自动装配是否打开,默认是true可以通过application.yml设置
- 获取@EnableAutoConfiguration里的exclude和excludeName内容以便排除
- 获取需要自动装配的所有配置类,读取META-INF/spring.factories druid 数据库连接池的 Spring Boot Starter 就创建了META-INF/spring.factories文件
- 筛选满足@ConditionalOnXXX注解的类,生效才会被加载
- 该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中
- 方法实现 重要的getAutoConfigurationEntry()方法
- 实现了这个接口中的 selectImports方法
@ComponentScan
扫描的 Spring 对应的组件,如 @Componet,@Repository
- 我们可以通过 basePackages 等属性来细粒度的定制 @ComponentScan 自动扫描的范围,如果不指定,则默认Spring框架实现会从声明 @ComponentScan 所在类的package进行扫描,所以 SpringBoot 的启动类最好是放在根package下,我们自定义的类就放在对应的子package下,这样就可以不指定 basePackages
启动类中的main方法:org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String…)
- 从spring.factories配置文件中加载EventPublishingRunListener对象,该对象拥有SimpleApplicationEventMulticaster属性,即在SpringBoot启动过程的不同阶段用来发射内置的生命周期事件;
- spring-bean包下META-INF/spring.factories
- 准备环境变量,包括系统变量,环境变量,命令行参数,默认变量,servlet相关配置变量,随机值以及配置文件(比如application.properties)等;
- 而后就会去创建Environment——这个时候会去加载application配置文件
- 控制台打印SpringBoot的bannner标志;
- 根据不同类型环境创建不同类型的applicationcontext容器,因为这里是servlet环境,所以创建的是AnnotationConfigServletWebServerApplicationContext容器对象;
- 从spring.factories配置文件中加载FailureAnalyzers对象,用来报告SpringBoot启动过程中的异常;
- 为刚创建的容器对象做一些初始化工作,准备一些容器属性值等,对ApplicationContext应用一些相关的后置处理和调用各个ApplicationContextInitializer的初始化方法来执行一些初始化逻辑等;
- 刷新容器,这一步至关重要。比如调用bean factory的后置处理器,注册BeanPostProcessor后置处理器,初始化事件广播器且广播事件,初始化剩下的单例bean和SpringBoot创建内嵌的Tomcat服务器等等重要且复杂的逻辑都在这里实现,主要步骤可见代码的注释,关于这里的逻辑会在以后的spring源码分析专题详细分析;
- // 1)在context刷新前做一些准备工作,比如初始化一些属性设置,属性合法性校验和保存容器中的一些早期事件等;
- // 2)让子类刷新其内部bean factory,注意SpringBoot和Spring启动的情况执行逻辑不一样
- // 3)对bean factory进行配置,比如配置bean factory的类加载器,后置处理器等
- // 4)完成bean factory的准备工作后,此时执行一些后置处理逻辑,子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置
- // 在这一步,所有的bean definitions将会被加载,但此时bean还不会被实例化
- // 5)执行BeanFactoryPostProcessor的方法即调用bean factory的后置处理器:
- // BeanDefinitionRegistryPostProcessor(触发时机:bean定义注册之前)和BeanFactoryPostProcessor(触发时机:bean定义注册之后bean实例化之前)
- // 6)注册bean的后置处理器BeanPostProcessor,注意不同接口类型的BeanPostProcessor;在Bean创建前后的执行时机是不一样的
- // 7)初始化国际化MessageSource相关的组件,比如消息绑定,消息解析等
- // 8)初始化事件广播器,如果bean factory没有包含事件广播器,那么new一个SimpleApplicationEventMulticaster广播器对象并注册到bean factory中
- // 9)AbstractApplicationContext定义了一个模板方法onRefresh,留给子类覆写,比如ServletWebServerApplicationContext覆写了该方法来创建内嵌的tomcat容器
- // 10)注册实现了ApplicationListener接口的监听器,之前已经有了事件广播器,此时就可以派发一些early application events
- // 11)完成容器bean factory的初始化,并初始化所有剩余的单例bean。这一步非常重要,一些bean postprocessor会在这里调用。
- // 12)完成容器的刷新工作,并且调用生命周期处理器的onRefresh()方法,并且发布ContextRefreshedEvent事件
- 执行刷新容器后的后置处理逻辑,注意这里为空方法;
- 调用ApplicationRunner和CommandLineRunner的run方法,我们实现这两个接口可以在spring容器启动后需要的一些东西比如加载一些业务数据等;
- 报告启动异常,即若启动过程中抛出异常,此时用FailureAnalyzers来报告异常;
- 最终返回容器对象,这里调用方法没有声明对象来接收。
public static void main(String[] args) throws Exception {
SpringApplication.run(new Class<?>[0], args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 新建SpringApplication对象,再调用run方法
return new SpringApplication(primarySources).run(args);
}
public ConfigurableApplicationContext run(String... args) {
// stopWatch用于统计run启动过程时长
StopWatch stopWatch = new StopWatch();
// 开始计时
stopWatch.start();
// 创建ConfigurableApplicationContext对象
ConfigurableApplicationContext context = null;
// exceptionReporters集合用来存储SpringApplication启动过程的异常,SpringBootExceptionReporter且通过spring.factories方式来加载
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置headless属性
configureHeadlessProperty();
/**
* 从spring.factories配置文件中加载到EventPublishingRunListener对象并赋值给SpringApplicationRunListeners
* # Run Listeners
* org.springframework.boot.SpringApplicationRunListener=\
* org.springframework.boot.context.event.EventPublishingRunListener
*/
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动SpringApplicationRunListeners监听
listeners.starting();
try {
// 创建ApplicationArguments对象,封装了args参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 备配置参数有app.properties,外部配置参数比如jvm启动参数等
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置spring.beaninfo.ignore属性
configureIgnoreBeanInfo(environment);
// 打印springboot的bannner
Banner printedBanner = printBanner(environment);
// 根据不同类型创建不同类型的spring applicationcontext容器
context = createApplicationContext();
/**
* 异常报告
* 从spring.factories配置文件中加载exceptionReporters,其中ConfigurableApplicationContext.class作为FailureAnalyzers构造方法的参数
* # Error Reporters
* org.springframework.boot.SpringBootExceptionReporter=\
* org.springframework.boot.diagnostics.FailureAnalyzers
*/
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备容器事项:调用各个ApplicationContextInitializer的initialize方法
// 和触发SpringApplicationRunListeners的contextPrepared及contextLoaded方法等
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器,这一步至关重要
refreshContext(context);
// 执行刷新容器后的后置处理逻辑,注意这里为空方法
afterRefresh(context, applicationArguments);
// 停止stopWatch计时
stopWatch.stop();
// 打印springboot的启动时常
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 触发SpringApplicationRunListener的started方法,通知spring容器已经启动
listeners.started(context);
// 调用ApplicationRunner和CommandLineRunner的run方法,实现spring容器启动后需要做的一些东西
callRunners(context, applicationArguments);
}
// 若上面的方法抛出异常,将异常添加到exceptionReporters集合中,并抛出 IllegalStateException 异常。
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 当容器刷新完毕等,触发SpringApplicationRunListeners数组的running方法
listeners.running(context);
}
catch (Throwable ex) {
// 若上面的方法抛出异常,将异常添加到exceptionReporters集合中,并抛出 IllegalStateException 异常。
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
怎么让Spring把Body变成一个对象
- @RequestBody注解原理
- 详细看springmvc的处理流程
SpringBoot的starter实现原理是什么?
原理就是因为在@EnableAutoConfiguration注解,会自动的扫描jar包下的META-INF/spring.factories文件的配置类,写在这里面的类都是需要被自动加载的
将configuration类中定义的bean加入spring到容器中。就相当于加载之前我们自己配置组件的xml文件。而现在SpringBoot自己定义了一个默认的值,然后直接加载进入了Spring容器。
SpringBoot提供的自动配置依赖模块都以spring-boot-starter-为命名前缀,并且这些依赖都在org.springframework.boot下。 所有的spring-boot-starter都有约定俗成的默认配置,但允许调整这些配置调整默认的行为。
spring 和springboot的区别
Spring Boot基本上是Spring框架的扩展,它消除了设置Spring应用程序所需的XML配置,为更快,更高效的开发生态系统铺平了道路。
Spring Boot中的一些特征:
- 创建独立的Spring应用。
- 嵌入式Tomcat、Jetty、 Undertow容器(无需部署war文件)。
- 提供的starters 简化构建配置
- 尽可能自动配置spring应用。
- 提供生产指标,例如指标、健壮检查和外部化配置
- 完全没有代码生成和XML配置要求
Maven依赖
首先,让我们看一下使用Spring创建Web应用程序所需的最小依赖项
<dependency> |
与Spring不同,Spring Boot只需要一个依赖项来启动和运行Web应用程序:
<dependency> |
在进行构建期间,所有其他依赖项将自动添加到项目中。
另一个很好的例子就是测试库。我们通常使用Spring Test,JUnit,Hamcrest和Mockito库。在Spring项目中,我们应该将所有这些库添加为依赖项。但是在Spring Boot中,我们只需要添加spring-boot-starter-test依赖项来自动包含这些库。
spring在运行前需要使用xml文件做很多配置,而springboot帮我们实现了这些配置的自动加载,基于注解和简单的yml配置即可
spring的web程序还是打包为war然后再Tomcat里运行,而springboot内嵌了Tomcat直接打成可运行的jar
Spring Boot 可执行 Jar 包运行原理
Spring Boot 有一个很方便的功能就是可以将应用打成可执行的 Jar。那么大家有没想过这个 Jar 是怎么运行起来的呢?本篇博客就来介绍下 Spring Boot 可执行 Jar 包的运行原理。
打可执行 Jar 包
将 Spring Boot 应用打成可执行 Jar包很容易,只需要在 pom 中加上一个 Spring Boot 提供的插件,然后在执行mvn package即可
<build> |
运行完mvn package后,我们会在 target 目录下看到两个 jar 文件。myproject-0.0.1-SNAPSHOT.jar 和 myproject-0.0.1-SNAPSHOT.jar.original。第一个 jar 文件就是我们应用的可执行 jar 包,第二个 Jar 文件是应用原始的 jar 包。
可执行 Jar 包内部结构
可执行 jar 目录结构 |
我们先来重点关注两个地方:META-INF 下面的 Jar 包描述文件和 BOOT-INF 这个目录。
Manifest-Version: 1.0 |
在上面我们看到一个熟悉的配置Main-Class: org.springframework.boot.loader.JarLauncher。我们大概能猜到这个类是整个系统的入口。
再看下 BOOT-INF 这个目录下面,我们会发现里面是我们项目打出来的 class 文件和项目依赖的 Jar 包。看到这里,你可能已经猜到 Spring Boot 是怎么启动项目的了。
JarLauncher
public class JarLauncher extends ExecutableArchiveLauncher { |
//launch方法 |
简单总结
- Spring Boot 可执行 Jar 包的入口点是 JarLauncher 的 main 方法;
- 这个方法的执行逻辑是先创建一个 LaunchedURLClassLoader,这个加载器加载类的逻辑是:先判断根类加载器和扩展类加载器能否加载到某个类,如果都加载不到就从 Boot-INF 下面的 class 和 lib 目录下去加载;
- 读取Start-Class属性,通过反射机制调用启动类的 main 方法,这样就顺利调用到我们开发的 Spring Boot 主启动类的 main 方法了。