【Spring面试】十、SpringBoot相关

文章目录

  • Q1、谈谈对SpringBoot的理解,它有哪些特性?
  • Q2、Spring和SpringBoot的关系和区别是什么?
  • Q3、SpringBoot的核心注解
  • Q4、SpringBoot的自动配置原理
  • Q5、为什么SpringBoot的jar包可以直接java -jar运行?
  • Q6、SpringBoot的启动原理?
  • Q7、SpringBoot内置Tomcat的启动原理是什么?
  • Q8、SpringBoot外部Tomcat启动原理?
  • Q9、如何自定义一个Starter?
  • Q10、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应用
    【Spring面试】十、SpringBoot相关_第1张图片

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

  • @EnableAutoConfiguration:向Spring容器中导入一个Selector,用来加载类路径下的SpringFactories中定义的自动配置类,并将这些自动加载为配置Bean
    【Spring面试】十、SpringBoot相关_第2张图片
    此外,SpringBoot还有一些注解,用于进行定制开发(当满足什么条件时才启用):

  • @ConditionalOnBean

  • @ConditionalOnMissingBean

  • @ConditionalOnClass

  • @ConditionalOnExpression

  • @ConditionalOnMissingBean

Q4、SpringBoot的自动配置原理

【Spring面试】十、SpringBoot相关_第3张图片

答案:

  • 通过@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相关的类
    【Spring面试】十、SpringBoot相关_第4张图片

  • java -jar命令执行时,会去jar中找manifest文件,在那里面找到真正的启动类
    【Spring面试】十、SpringBoot相关_第5张图片
    【Spring面试】十、SpringBoot相关_第6张图片

  • Fat jar的启动Main方法是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载boot-lib下面所依赖的那些jar,并以一个新线程启动应用的Main方法(找到manifest文件中的Start-Class)
    【Spring面试】十、SpringBoot相关_第7张图片
    【Spring面试】十、SpringBoot相关_第8张图片
    【Spring面试】十、SpringBoot相关_第9张图片【Spring面试】十、SpringBoot相关_第10张图片

Q6、SpringBoot的启动原理?

【Spring面试】十、SpringBoot相关_第11张图片
此问题即run方法背后在做些什么?

答案:

  • 运行main方法:初始化SpringApplication,从spring.factories中读取listener、ApplicationContextInitializer
    【Spring面试】十、SpringBoot相关_第12张图片

  • 运行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>
  • 将内置Tomcat的作用范围修改成provided

【Spring面试】十、SpringBoot相关_第13张图片

<dependency>
   <groupId>org.springframework.bootgroupId>
   <artifactId>spring-boot-starter-tomcatartifactId>
   <scope>providedscope>
dependency>
  • 自定义一个类继承 SpringBootServletInitializer 重写其configure()方法,并传入SpringBoot项目主程序的类
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 传入SpringBoot的主程序类
        return builder.sources(DemoApplication.class);
    }
}

答案:

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

【Spring面试】十、SpringBoot相关_第14张图片

  • @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);
            }

        }
    }
}

【Spring面试】十、SpringBoot相关_第15张图片

  • 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");
        }

    }

  • 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;
}

Q9、如何自定义一个Starter?

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

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

答案:

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

【Spring面试】十、SpringBoot相关_第16张图片

你可能感兴趣的:(面试,spring,面试,spring,boot)