# 面试题丨SpringBoot

https://segmentfault.com/a/1190000016686735

# 1. SpringBoot 和 Spring 的区别?

Spring是一个开源的轻量级的Java开发框架。

Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

# 2. 为什么使用 SpringBoot?

  • 独立运行
  • 简化配置
  • 自动配置
  • 无代码生成和XML配置
  • 应用监控
  • 上手容易
# 你觉得 SpringBoot 最大的优势是什么?
答:Spring Boot 的最大的优势是“约定优于配置”。“约定优于配置”是一种软件设计范式,开发人员按照约定的方式来进行编程,可以减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。
# Spring Boot 中 “约定优于配置“的具体产品体现在哪里?
答:Spring Boot Starter、Spring Boot Jpa 都是“约定优于配置“的一种体现。都是通过“约定优于配置“的设计思路来设计的,Spring Boot Starter 在启动的过程中会根据约定的信息对资源进行初始化;Spring Boot Jpa 通过约定的方式来自动生成 Sql ,避免大量无效代码编写。

# 3. SpringBoot 的自动装配原理

  1. 首先我们在主启动类上加注解 @SpringBootApplication

  2. 然后在主启动类中的 main() 方法中用 SpringApplication.run() 方法来启动 SpringBoot 应用,生成一个 ApplicationContext 对象来管理 Spring 容器中的 Bean 对象。

  3. 自动装配的核心就在 @SpringBootApplication 这个注解上,进去以后会发现这个注解上面除了 4 个 Java 的元注解外还有 3 个注解非常重要:

    • @SpringBootConfiguration:这个注解的底层是一个 @Configuration 注解,表明这是一个配置类,被 @Configuration 注解修饰的类是一个 IOC 容器,支持 JavaConfig 的方式来进行配置;
    • @EnableAutoConfiguration:这个注解表明启动自动装配,里面除了 Java 的 4 个元注解还包含两个比较重要的注解 @AutoConfigurationPackage@Import
    • @ComponentScan:这个就是扫描注解的意思,默认扫描当前类所在的包及其子包下包含的注解,将@Controller/@Service/@Component/@Repository 等注解加载到IOC容器中;
  4. 现在就来看跟自动装配联系最紧密的注解 @EnableAutoConfiguration 里面的 2 重要注解 @AutoConfigurationPackage@Import

  5. @AutoConfigurationPackage@ComponentScan 一样,也是将主配置类所在的包及其子包里面的组件扫描到 IoC 容器中,但是区别是 @AutoConfigurationPackage 扫描 @Enitity@MapperScan 等第三方依赖的注解,@ComponentScan 只扫描@Controller/@Service/@Component/@Repository 这些常见注解。所以这两个注解扫描的对象是不一样的。

  6. @Import(AutoConfigurationImportSelector.class) 是自动装配的核心注解,AutoConfigurationImportSelector.class 中有个 selectImports() 方法:

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
          return NO_IMPORTS;
       }
       AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
       return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    

    selectImports() 方法还调用了 getCandidateConfigurations() 方法,这个方法就是负责去加载一些候选的配置:

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
          return EMPTY_ENTRY;
       }
       AnnotationAttributes attributes = getAttributes(annotationMetadata);
       List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
       configurations = removeDuplicates(configurations);
       Set<String> exclusions = getExclusions(annotationMetadata, attributes);
       checkExcludedClasses(configurations, exclusions);
       configurations.removeAll(exclusions);
       configurations = getConfigurationClassFilter().filter(configurations);
       fireAutoConfigurationImportEvents(configurations, exclusions);
       return new AutoConfigurationEntry(configurations, exclusions);
    }
    
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
    				getBeanClassLoader());
    		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
    				+ "are using a custom packaging, make sure that file is correct.");
    		return configurations;
    	}
    

    getCandidateConfigurations()方法要做的就是找 META-INF/spring.factories 文件的配置类。

  7. 我们定位到 META-INFO/spring.factories 这里文件,可以发现里面都是一些常见的组件:

    spring.factories 文件是一组组的 key=value 的形式,包含了 key 为 EnableAutoConfiguration 的全类名,value 是一个AutoConfiguration 类名的列表,以逗号分隔。

    image-20210401152746621

  8. 最终,@EnableAutoConfiguration 注解通过 @SpringBootApplication 注解被间接的标记在了 SpringBoot 的启动类上,SpringApplicaton.run() 方法的内部就会执行 selectImports() 方法,进而找到所有 JavaConfig 配置类全限定名对应的 class,然后将所有自动配置类加载到 IoC容器中。

  9. 那么这些类是如何获取默认属性值的呢?以 ServletWebServerFactoryAutoConfiguration 为例,它是 Servlet 容器的自动配置类。

    @Configuration(proxyBeanMethods = false)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @ConditionalOnClass(ServletRequest.class)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @EnableConfigurationProperties(ServerProperties.class)
    @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
          ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
          ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
          ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
    public class ServletWebServerFactoryAutoConfiguration {
    

    该类上开启了 @EnableConfigurationProperties(ServerProperties.class) 注解,最终找到了 ServerProperties 类。

  10. 至此,我们大致可以了解。在全局配置的属性如:server.port 等,通过 @ConfigurationProperties 注解,绑定到对应的XxxxProperties 配置实体类上封装为一个 bean,然后再通过 @EnableConfigurationProperties注解导入到 Spring 容器中。

    @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
    public class ServerProperties {
    
       /**
        * Server HTTP port.
        */
       private Integer port;
    
       /**
        * Network address to which the server should bind.
        */
       private InetAddress address;
    

总结:

SpringBoot启动的时候通过 @EnableAutoConfiguration 注解找到 META-INF/spring.factories 文件中的所有自动配置类,并对其加载,这些自动配置类都是以 AutoConfiguration 结尾来命名的。它实际上就是一个 JavaConfig 形式的 IoC 容器配置类,通过以 Properties 结尾命名的类中取得在全局配置文件中配置的属性,如 server.port。

*Properties 类的含义:封装配置文件的相关属性。

*AutoConfiguration 类的含义:自动配置类,添加到 IoC 容器中。

# 4. SpringBoot 启动流程

img

1、新建 module,在主程序类加入断点,启动springboot

2、首先进入 SpringAplication 类 run() 方法

3、run() 方法新建 SpringApplication 对象

4、SpringApplication 对象的 run() 方法,首先创建并启动计时监控类

5、接着通过 configureHeadlessProperty 设置 java.awt.headless 的值

6、接着调用getRunListeners 创建所有 Spring 监听器

7、接着 DefaultApplicationArguments 初始化应用应用参数

8、接着 prepareEnvironment 根据运行监听器和参数准备 Spring 环境

9、接着调用 createApplicationContext 方法创建应用上下文

10、通过 prepareContext 准备应用上下文

11、refreshContext 方法刷新上下文

12、调用 stop() 方法停止计时监控器类

13、调用 started 发布应用上下文启动完成事件

14、callRunners 方法执行所有 runner 运行器

15、调用 running 发布应用上下文就绪事件

16、最后返回应用上下文

# 5. SpringBoot 的核心配置文件有哪几个?它们的区别是什么?

Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。

application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。

bootstrap 配置文件有以下几个应用场景。

  • 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
  • 一些固定的不能被覆盖的属性;
  • 一些加密/解密的场景;

# 6. 你如何理解 SpringBoot 中的 Starters?

Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成 Spring 及其他技术,而不需要到处找示例代码和依赖包。如你想使用 Spring JPA 访问数据库,只要加入 spring-boot-starter-data-jpa 启动器依赖就能使用了。

Starters包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。

# 7. 怎么设计无状态服务?

对于无状态服务,首先说一下什么是状态:如果一个数据需要被多个服务共享,才能完成一笔交易,那么这个数据被称为状态。进而依赖这个“状态”数据的服务被称为有状态服务,反之称为无状态服务。

无状态服务(stateless service)对单次请求的处理,不依赖其他请求,也就是说,处理一次请求所需的全部信息,要么都包含在这个请求里,要么可以从外部获取到(比如说数据库),服务器本身不存储任何信息

有状态服务(stateful service)则相反,它会在自身保存一些数据,先后的请求是有关联的。

那么这个无状态服务原则并不是说在微服务架构里就不允许存在状态,表达的真实意思是要把有状态的业务服务改变为无状态的计算类服务,那么状态数据也就相应的迁移到对应的“有状态数据服务”中。

场景说明:例如我们以前在本地内存中建立的数据缓存、Session缓存,到现在的微服务架构中就应该把这些数据迁移到分布式缓存中存储,让业务服务变成一个无状态的计算节点。迁移后,就可以做到按需动态伸缩,微服务应用在运行时动态增删节点,就不再需要考虑缓存数据如何同步的问题。

# 8. SpringBoot 如何设置支持跨域请求?

现代浏览器出于安全的考虑, HTTP 请求时必须遵守同源策略,否则就是跨域的 HTTP 请求,默认情况下是被禁止的,IP(域名)不同、或者端口不同、协议不同(比如 HTTP、HTTPS)都会造成跨域问题。

前端的解决方法

使用 JSONP 来支持跨域的请求,JSONP 实现跨域请求的原理简单的说,就是动态创建

上次更新: 8/28/2022, 11:43:26 PM