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包版本。

image-20211102095750486

  • 我们在写或者引入一些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---->

image-20230623203158360

1)首先介绍getSpringFactoriesLoaderFactoryClass()函数:

就是标注了EnableAutoConfiguation的这个类:

我们@SpringBootApplication就标注了它

image-20230623203221895

因此可以知道兜兜转转的目的就是将启动类下的所有资源进行导入。

2)接下来在getCandidateConfigurations中还有一个方法Assert.notEmpty()方法:

再点击进入loadFactoryNames方法当中:(该类在SpringFactoriesLoader类当中

image-20230623203236800

META-INF/spring:自动配置的核心文件:

image-20230623203246784

会发现下面有众多的包文件配置:

image-20230623161534898

选取一个WebMvcAutoConfiguration进入查看:

image-20230623161747353

结论:

​ springboot所有的自动配置都是在启动类中被扫描并且加载。扫描META-INF/factories,所有的自动配置类都在这里,但不一定生效,要判断条件是否成立,

主要到导入了对应的starter,就有了对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功。

image-20230623164229460

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插件引入后自动添加的。

image-20230623180830088

如果想看JarLauncher类的源码需要手动引入‘spring-boot-loader’依赖,其实声明‘spring-boot-maven-plugin’插件也会自动引入其依赖,但不能查看到源码内容:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-loader</artifactId>
</dependency>

打开之后可以看到此类有一个主程序入口,main方法

image-20230623180933773

以下就是源码解析部分:

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’的值(类),并进行调用:(有两种方式)

image-20230623181110060

第一种: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方法调用后发生了什么事情?

image-20230623184856521

注意:

1.new SpringApplication所做的事情:

image-20230623183725994

上面的图片也就是SpringBoot启动后的初始化的阶段:

(1)从Spring.factories中读取了ApplicationListener监听器
(2)从Spring.factories中读取了ApplicationInitializer初始化器
(3)同时将main方法所在的类放入mainApplicationClass当中

2.run()方法所做的事情:
最最主要的事情还是加载我们IOC容器进行初始化:

image-20230623185044018

包括这一步还做了一下的内容:

image-20230623190049054

5. SpringBoot内置的Tomcat启动原理:

1.注意SpringBoot我们需要添加web的场景启动器。就是xml中的web依赖:

 <!--启动器-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
</dependency>

注意SpringBoot会在其中添加ServletWebServerFactoryAutoConfiguration servlet的容器自动配置类,会决定使用哪一个web容器,例如下面:

image-20230623203328989

  • 该自动配置类通过@Import导入可用(可用@ConditionOnClass)判断决定使用哪一个web容器工厂(默认是Tomcat)
  • 在内嵌Tomcat类中配置一个TomcatServletWebServerFactory的Bean工厂
  • 他会在SpringBoot容器启动时加载ioc容器refresh 在onrefresh创建内嵌的Tomcat启动。

详细流程:

1.在自动配置类当中启用内嵌的Tomcat,配置一个Tomcat的配置工厂

image-20230623201230634

2.SpringApplication.run方法运行后会创建一个SpringBoot的容器叫做AnnotationConfigServletWebServerApplicationContext主要采用的是调用createApplicationContext方法调用进行创建:

image-20230623201558077

3.调用容器的refreshContext方法,加载IOC容器,底层就会调用Spring源码中最熟悉的源码方法refresh方法:

image-20230623201653039

4.解析自动配置类:

invokeBeanFactoryPostProcessor方法解析@Bean等注解,我们会发现第一步配置的TomcatServletWebServerFactory工厂上也使用@Bean注解,因此也会解析

5.在invokeBeanFactoryPostProcessor方法调用完毕后会调用onfresh方法,会调用ServletWebServerApplicationContext中的createWebServer方法,注意ServletWebServerApplicationContext为AnnotationConfigServletWebServerApplicationContext的父类,在createWebServer方法中会调用getWebServerFctory方法:

image-20230623202143622

ServletWebServerFactory的实现类:

image-20230623202204221

6.调用getWebServer方法:
image-20230623202325872

涉及到Tomcat创建的关键代码如下:

image-20230623202408473

调用Tomcat在getTomcatWebServer---->TomcatWebServer------>initialize():

this.tomcat.start();

image-20230623202552206

Tomcat挂起等待被调用:

startDaemonAwaitThread()

image-20230623202649616

posted @ 2023-06-23 20:34  LycCj  阅读(325)  评论(0编辑  收藏  举报