SpringBoot面试题
SpringBoot中常见的面试题:
1.SpringBoot中常用的注解有哪些:
对于理解SpringBoot的自动配置(自动装配)原理作出铺垫。
1.@SpringBootApplication:这个注解标识了SpringBoot的工程,这个注解标识了一个SpringBoot工程,它实际上是另外三个注解合成的。
2.@SpringBootConfiguration:这个注解实际就是一个@Configuration,表示启动类也是一个配置类
3.@EnableAutoConfiguration:向Spring容器中导入一个Selector,用来加载ClassPath下SpringFactories中所定义的自动配置类,将这些自动加载为配置的Bean
4.@ConditionalXXX也很关键:
(1)@ConditionalOnBean
(2)@ConditionalOnClass
(3)@ConditionalOnExpression
(4)@ConditionalOnMissingBean等等
2.SpringBoot中自动配置(自动装配)原理:
(1)pom.xml
- spring-boot-dependencies:核心依赖在父工程当中!我们点击Spring-boot-starter-parent然后又会有一个spring-boot-dependencies,我们使用ctrl+鼠标左键进入往下拉会看到大量的jar包版本。
- 我们在写或者引入一些Springboot依赖的时候,不需要指定版本仓库,因为有这些版本的仓库,同时里面存在一系列的自动的资源过滤的配置文件。不需要像Spring在配置资源过滤文件。
(2)启动器:
<!--启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
-
启动器说白了就是springboot的启动场景:
比如spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖!
-
springboot会将所有的功能场景变成一个个启动器(例如:Spring-boot-starter-web就会默认导入Tomcat的运行环境,不要再配置Tomcat)
-
如果我们要使用什么功能,就只需要找到其对应的启动器即可。在官网中可以找到starter的文件,说明文件中存在所有的需要的启动器的说明。
(3)主程序:
@SpringBootApplication:标注这个类是一个SpringBoot的应用:
package com.kuang;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//标注这个类是一个springboot的应用:
@SpringBootApplication
public class Springboot02HelloworldApplication {
public static void main(String[] args) {
//将springboot应用启动:反射加载该类的对象:
SpringApplication.run(Springboot02HelloworldApplication.class, args);
}
}
-
注解:对于SpringBootApplication注解的深入探析:
@SpringBootConfiguration:SpringBoot的配置 @Configuration:spring配置类 @Component:说明这也是一个spring的组件 @EnableAutoConfiguration:自动导入配置 @AutoConfigurationPackage:自动配置包 @Import({Registrar.class}):自动配置包注册 @Import({AutoConfigurationImportSelector.class}):自动配置导入选择: 注意AutoConfigurationImportSelector实现了DeferredImportSelector--
-
源码详细的解析:
AutoConfigurationImportSelector-------->getAutoConfigurationEntry
-------->getCandidateConfigurations-------->loadFactoryNames---->
1)首先介绍getSpringFactoriesLoaderFactoryClass()函数:
就是标注了EnableAutoConfiguation的这个类:
我们@SpringBootApplication就标注了它
因此可以知道兜兜转转的目的就是将启动类下的所有资源进行导入。
2)接下来在getCandidateConfigurations中还有一个方法Assert.notEmpty()方法:
再点击进入loadFactoryNames方法当中:(该类在SpringFactoriesLoader类当中)
META-INF/spring:自动配置的核心文件:
会发现下面有众多的包文件配置:
选取一个WebMvcAutoConfiguration进入查看:
结论:
springboot所有的自动配置都是在启动类中被扫描并且加载。扫描META-INF/factories,所有的自动配置类都在这里,但不一定生效,要判断条件是否成立,
主要到导入了对应的starter,就有了对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功。
3. 为什么springBoot的jar可以直接运行:
1.SpringBoot提供一个插件spring-boot-maven-plugin用于把程序打包成一个可执行的jar包。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
但是该jar包与一般的Java项目下的jar包并不同,此jar包可以直接用:
java-jar xxxx.jar命令直接运行并部署
2.查询原因:
SpringBoot应用打包成为jar包之后,生成的jar包是带有普通jar包文件的特殊的jar包,主要包含了应用程序依赖的jar包与SpringBoot loader相关的类。
我们可以解压形成的SpringBoot的jar包,在Boot-INF\lib目录下就有该项目代码依赖的jar包文件。
3.底层的原理:
在Spring Boot项目的jar中会生成一个MANIFEST.MF文件(路径:META-INF\MANIFEST.MF),打开该文件你会看到有一个MainClass的映射,其对应的值是一个类,就是执行‘java -jar’命令后正式执行的类,mainclass类是springboot插件引入后自动添加的。
如果想看JarLauncher类的源码需要手动引入‘spring-boot-loader’依赖,其实声明‘spring-boot-maven-plugin’插件也会自动引入其依赖,但不能查看到源码内容:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
打开之后可以看到此类有一个主程序入口,main方法
以下就是源码解析部分:
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
//自定义类加载器加载jar文件
ClassLoader classLoader = createClassLoader(getClassPathArchives());
//关注getMainClass方法
launch(args, getMainClass(), classLoader);
}
此处我们先去看下getMainClass方法,关键点在于getMainClass()方法,获取META-INF\MANIFEST.MF文件键为‘Start-Class’的值(类),并进行调用:(有两种方式)
第一种:ExecutableArchiveLauncher实现方式:
@Override
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
第二种:PropertiesLauncher实现方式:
@Override
protected String getMainClass() throws Exception {
String mainClass = getProperty(MAIN, "Start-Class");
if (mainClass == null) {
throw new IllegalStateException("No '" + MAIN + "' or 'Start-Class' specified");
}
return mainClass;
}
我们再回头去看一下launch方法:
/**
* mainClass参数就是前面getMainClass从MANIFEST.MF文件中获取到的Start-Class
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run(); //注意此处run方法调用!!
}
我们会发下其中就是调用的配置的主启动类的run方法:
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
最后是new了一个MainMethodRunner类,然后在createMainMethodRunner调用run方法,而在run方法中可以看到是通过反射去执行Start-Class对于类的main方法,开始执行业务逻辑:
public class MainMethodRunner {
private final String mainClassName;
private final String[] args;
/**
* Create a new {@link MainMethodRunner} instance.
* @param mainClass the main class
* @param args incoming arguments
*/
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null) ? args.clone() : null;
}
public void run() throws Exception {
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });
}
}
总结:面试时候按照如下的回答就可以:
1.源码的执行流程包括如下:
SpringBoot打包成jar包,会形成一个Fat jar的jar包文件-------->当我们运行java -jar 文件之后会首先找到Main-class配置信息
也就是Jarlauncher类-------->调用launch方法------>然后会调用getMainClass方法(实现的方式主要有两种)---->
主要从Manifest配置清单当中拿到start-class配置的信息(也就是我们的主启动类)------>进入launch方法会调用
createMainMethodRunner----->会首先创建MainMethodRunner对象----->使用MainMethodRunner执行run()方法(利用反射机制)
2.主要流程:
(1)SpringBoot提供一个插件Spring-boot-maven-plugin用于把程序打包成一个可执行的jar包文件
(2)SpringBoot应用打包成功后会形成一个Fat jar,包含了应用依赖的jar和SpringBoot loader相关的类
(3)java-jar会去manifest清单文件中找真正的主启动类(Main-class)
(4)Fat jar的启动Main函数是Jarlauncher,它负责创建一个新的线程来寻找项目的主启动类也就是start-class配置的信息,并通过
MainMethodRunner创建新对象并调用项目主启动类的run方法运行项目。
4. SpringBoot的启动流程:
SpingBoot的启动流程也可以通俗的说成主启动类的run方法调用后发生了什么事情?
注意:
1.new SpringApplication所做的事情:
上面的图片也就是SpringBoot启动后的初始化的阶段:
(1)从Spring.factories中读取了ApplicationListener监听器
(2)从Spring.factories中读取了ApplicationInitializer初始化器
(3)同时将main方法所在的类放入mainApplicationClass当中
2.run()方法所做的事情:
最最主要的事情还是加载我们IOC容器进行初始化:
包括这一步还做了一下的内容:
5. SpringBoot内置的Tomcat启动原理:
1.注意SpringBoot我们需要添加web的场景启动器。就是xml中的web依赖:
<!--启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
注意SpringBoot会在其中添加ServletWebServerFactoryAutoConfiguration servlet的容器自动配置类,会决定使用哪一个web容器,例如下面:
- 该自动配置类通过@Import导入可用(可用@ConditionOnClass)判断决定使用哪一个web容器工厂(默认是Tomcat)
- 在内嵌Tomcat类中配置一个TomcatServletWebServerFactory的Bean工厂
- 他会在SpringBoot容器启动时加载ioc容器refresh 在onrefresh创建内嵌的Tomcat启动。
详细流程:
1.在自动配置类当中启用内嵌的Tomcat,配置一个Tomcat的配置工厂
2.SpringApplication.run方法运行后会创建一个SpringBoot的容器叫做AnnotationConfigServletWebServerApplicationContext主要采用的是调用createApplicationContext方法调用进行创建:
3.调用容器的refreshContext方法,加载IOC容器,底层就会调用Spring源码中最熟悉的源码方法refresh方法:
4.解析自动配置类:
invokeBeanFactoryPostProcessor方法解析@Bean等注解,我们会发现第一步配置的TomcatServletWebServerFactory工厂上也使用@Bean注解,因此也会解析
5.在invokeBeanFactoryPostProcessor方法调用完毕后会调用onfresh方法,会调用ServletWebServerApplicationContext中的createWebServer方法,注意ServletWebServerApplicationContext为AnnotationConfigServletWebServerApplicationContext的父类,在createWebServer方法中会调用getWebServerFctory方法:
ServletWebServerFactory的实现类:
6.调用getWebServer方法:
涉及到Tomcat创建的关键代码如下:
调用Tomcat在getTomcatWebServer---->TomcatWebServer------>initialize():
this.tomcat.start();
Tomcat挂起等待被调用:
startDaemonAwaitThread()