• 【Spring面试】十、SpringBoot相关


    Q1、谈谈对SpringBoot的理解,它有哪些特性?

    答案:

    SpringBoot的用来快速开发Spring应用的一个脚手架、其设计目的是用来简化新spring应用的初始搭建以及开发过程

    • SpringBoot提供了很多内置的starter结合自动配置,对主流框架无配置集成、开箱即用
    • SpringBoot简化了开发,采用Javaconfig的方式可以使用零xml的方式进行开发
    • SpringBoot内置web容器无需依赖外部wep服务器,省略了web.xml,直接运行jar文件就可以启动web应用
    • SpringBoot帮我管理了常用的第三方依赖的版本,减少出现版本冲突的问题
    • SpringBoot自带了监控功能,可以监控应用程序的运行状况,或者内存、线程池、Htp 请求统计等,同时还提供了优雅关闭应用程序等功能

    Q2、Spring和SpringBoot的关系和区别是什么?

    答案:

    • SpringBoot是Spring生态的产品
    • Spring Framework是一个容器框架
    • SpringBoot 它不是一个框架、它是一个可以快速构建基于Spring的脚手架(里面包含了Spring和各种框架),为开发Spring生态其他框架铺平道路

    二者不是一个层面的东西,没有可比性。

    Q3、SpringBoot的核心注解

    答案:

    • @SpringBootApplication:常用于启动类,标记这个应用是一个SpringBoot应用
      在这里插入图片描述

    • @SpringBootConfiguration:这个注解其实就是@Configuration,表示启动类是一个配置类

    • @EnableAutoConfiguration:向Spring容器中导入一个Selector,用来加载类路径下的SpringFactories中定义的自动配置类,并将这些自动加载为配置Bean
      在这里插入图片描述
      此外,SpringBoot还有一些注解,用于进行定制开发(当满足什么条件时才启用):

    • @ConditionalOnBean

    • @ConditionalOnMissingBean

    • @ConditionalOnClass

    • @ConditionalOnExpression

    • @ConditionalOnMissingBean

    Q4、SpringBoot的自动配置原理

    在这里插入图片描述

    答案:

    • 通过@SpringBootApplication 引入了@EnableAutoConfiguration (负责启动自动配置功能)
    • @EnableAutoConfiguration 引入了@lmport
    • Spring容器启动时(加载loc容器时)会解析@Import 注解
    • 上面的@lmport导入了一个DeferredlmportSelector,它会使SpringBoot的自动配置类的顺序在最后,这样方便我们扩展和覆盖
    • 然后读取所有的/META-INF/spring.factories文件
    • 过滤出所有AutoConfigurtionClass类型的类
    • 最后通过@ConditionOnXXX排除无效的自动配置类

    Q5、为什么SpringBoot的jar包可以直接java -jar运行?

    普通的jar包直接运行,报错:jar包中没有主清单属性

    在这里插入图片描述

    答案:

    • SpringBoot提供了一个插件spring-boot-maven-plugin,用于把程序打包成一个可执行的jar包

    • SpringBoot应用打包后,生成一个Fat jar(jar包中包含jar),包含了应用依赖的jar包和SpringBoot loader相关的类
      在这里插入图片描述

    • java -jar命令执行时,会去jar中找manifest文件,在那里面找到真正的启动类
      在这里插入图片描述
      在这里插入图片描述

    • Fat jar的启动Main方法是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载boot-lib下面所依赖的那些jar,并以一个新线程启动应用的Main方法(找到manifest文件中的Start-Class)
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述在这里插入图片描述

    Q6、SpringBoot的启动原理?

    在这里插入图片描述
    此问题即run方法背后在做些什么?

    答案:

    • 运行main方法:初始化SpringApplication,从spring.factories中读取listener、ApplicationContextInitializer
      在这里插入图片描述

    • 运行run方法

    • 读取环境变量、配置信息…

    • 创建SpringApplication上下文:ServletWebServerApplicationContext

    • 预初始化上下文 :读取启动类,将启动类做为配置类读取,注册为BeanDefinition

    • 调用refresh方法加载IoC容器

      • invokeBeanFactoryPostProcessor – 解析@lmport: 加载所有的自动配置类
      • onRefresh 创建(内置)servlet容器(默认tomcat)
    • 在这个过程中springboot会调用很多监听器对外进行扩展

    Q7、SpringBoot内置Tomcat的启动原理是什么?

    答案:

    • 当添加了spring-boot-starter-web依赖后,SpringBoot中会添加ServletWebServerFactoryAutoConfiguration servlet容器自动配置类
    • 这个自动配置类通过@Import导入了可用的一个web容器工厂,默认tomcat(通过@ConditionalOnClass判断决定使用哪一个)
    • 在内嵌Tomcat类中配置了一个TomcatServletWebServerFactory的Bean (Web容器工厂)
    • 它会在SpringBoot启动时,加载ioc容器(refresh) OnRefersh 创建内嵌的Tomcat并启动

    Q8、SpringBoot外部Tomcat启动原理?

    先说下实现:

    • 在pom文件中将打包方式改为war(后续要交给外部的tomcat运行)
    <packaging>war</packaging>
    
    • 1
    • 将内置Tomcat的作用范围修改成provided

    在这里插入图片描述

    <dependency>
       <groupId>org.springframework.bootgroupId>
       <artifactId>spring-boot-starter-tomcatartifactId>
       <scope>providedscope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 自定义一个类继承 SpringBootServletInitializer 重写其configure()方法,并传入SpringBoot项目主程序的类
    public class ServletInitializer extends SpringBootServletInitializer {
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
            // 传入SpringBoot的主程序类
            return builder.sources(DemoApplication.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    答案:

    • 根据Servlet3.0规范,服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例
    • ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名

    在这里插入图片描述

    • @HandlesTypes注解,在应用启动的时候加载我们感兴趣的类WebApplicationInitializer.class,上面自己继承的类的父类就是WebApplicationInitializer.class
    @HandlesTypes({WebApplicationInitializer.class})
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
        public SpringServletContainerInitializer() {
        }
    
        public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
            List<WebApplicationInitializer> initializers = new LinkedList();
            Iterator var4;
            if (webAppInitializerClasses != null) {
                var4 = webAppInitializerClasses.iterator();
    
                while(var4.hasNext()) {
                    Class<?> waiClass = (Class)var4.next();
                    if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                        try {
                            // 将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set>;为这些WebApplicationInitializer类型的类创建实例。
                            initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                        } catch (Throwable var7) {
                            throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                        }
                    }
                }
            }
    
            if (initializers.isEmpty()) {
                servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            } else {
                servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
                AnnotationAwareOrderComparator.sort(initializers);
                var4 = initializers.iterator();
    
                while(var4.hasNext()) {
                    WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                    // 每一个WebApplicationInitializer都调用自己的onStartup()
                    initializer.onStartup(servletContext);
                }
    
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    在这里插入图片描述

    • onStartup方法,创建web应用容器
    public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
        public void onStartup(ServletContext servletContext) throws ServletException {
            this.logger = LogFactory.getLog(this.getClass());
            // 创建web应用容器
            WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
            if (rootAppContext != null) {
                servletContext.addListener(new ContextLoaderListener(rootAppContext) {
                    public void contextInitialized(ServletContextEvent event) {
                    }
                });
            } else {
                this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
            }
    
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • createRootApplicationContext方法里调用一开始重写的configure方法,拿到SpringBoot主程序并启动
    protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
            // 1、创建SpringApplicationBuilder
            SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
            StandardServletEnvironment environment = new StandardServletEnvironment();
            // 2、准备环境
            environment.initPropertySources(servletContext, (ServletConfig)null);
            builder.environment(environment);
            builder.main(this.getClass());
            ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
            if (parent != null) {
                this.logger.info("Root context already created (using as parent).");
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
                builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
            }
            // 3、初始化
            builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
            builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
            // 4、调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
            builder = this.configure(builder);
    
            SpringApplication application = builder.build();
            if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
                application.addPrimarySources(Collections.singleton(this.getClass()));
            }
    
            Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
            if (this.registerErrorPageFilter) {
                application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
            }
            // 5、启动Spring应用
            return this.run(application);
        }
    }
    
    // 此方法被启动引导类 ServletInitializer有方法重写, 传入的是应用构建器SpringApplicationBuilder, 也就是SpringBoot的主程序类
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
            return builder;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    Q9、如何自定义一个Starter?

    https://www.cnblogs.com/hello-shf/p/10864977.html

    Q10、SpringBoot读取配置文件的原理是什么?加载顺序是怎样的?

    答案:

    通过事件监听的方式读取配置文件,同一个配置,优先级高的覆盖优先级低的,不同配置则取并集。

    在这里插入图片描述

  • 相关阅读:
    C++11、17、20的内存管理-指针、智能指针和内存池从基础到实战(上)
    dubbo是如何实现可扩展的?
    [学习笔记]Node2Vec图神经网络论文精读
    Gang scheduling istn‘t worth it ...yet
    Pycharm的安装和使用
    窗函数总结
    数据仓库---Hive
    2023年中国铝压延产量、销售收入及市场规模分析[图]
    Java监听器知识点总结-DX的笔记
    【算法100天 | 19】链表拆分、深拷贝
  • 原文地址:https://blog.csdn.net/llg___/article/details/132838931