SpringBoot 常见面试题总结

简单介绍一下 Spring?有啥缺点?

  • Spring 是一个重量级企业开发框架,是 Enterprise JavaBean(EJB)的替代品。Spring 为企业级 Java 开发提供了一种相对简单的方法,通过 依赖注入面向切面编程(AOP),用简单的 Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。
  • 缺点:虽然 Spring 的组件代码是轻量级的,但它的配置(特别是 XML 配置)可能会显得比较繁琐和复杂。

为什么要有 SpringBoot?

  • Spring 旨在简化 J2EE 企业应用程序开发,而 Spring Boot 进一步简化了 Spring 开发,减少了配置文件,提供了开箱即用的开发体验。

使用 Spring Boot 的主要优点

  • 开发基于 Spring 的应用程序更加简便。
  • Spring Boot 项目的开发或工程时间显著减少,提高了整体生产力。
  • 不需要编写大量样板代码、XML 配置和注解。
  • Spring Boot 遵循“固执己见的默认配置”,减少开发工作量。
  • Spring Boot 应用程序提供嵌入式 HTTP 服务器(如 Tomcat 和 Jetty),可以轻松开发和测试 Web 应用程序。
  • 提供命令行接口(CLI)工具,用于开发和测试 Spring Boot 应用程序,如 Java 或 Groovy。

什么是 Spring Boot Starters?

  • Spring Boot Starters 是一系列依赖关系的集合,简化了项目的依赖管理。
  • 例如:开发 REST 服务或 Web 应用程序时,只需添加一个 spring-boot-starter-web 依赖,它会包含开发 REST 服务所需的所有依赖。

Spring Boot 支持哪些内嵌 Servlet 容器?

  • Spring Boot 支持以下内嵌的 Servlet 容器:
    • Tomcat
    • Jetty
    • Undertow

如何在 Spring Boot 应用程序中使用 Jetty 而不是 Tomcat?

  • Spring Boot 默认使用 Tomcat 作为嵌入式 Servlet 容器。要使用 Jetty,只需在 pom.xml(Maven)或 build.gradle(Gradle)中移除 spring-boot-starter-tomcat 并添加 spring-boot-starter-jetty 依赖。

介绍一下 @SpringBootApplication 注解

  • @SpringBootApplication 是一个组合注解,包含了以下三个注解:
    • @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类。
    • @EnableAutoConfiguration:启用 Spring Boot 的自动配置机制。
    • @ComponentScan:扫描被 @Component(如 @Service, @Controller)注解的 bean,默认会扫描该类所在的包下所有的类。

Spring Boot 的自动配置是如何实现的?

  • @EnableAutoConfiguration 是启动自动配置的关键。
  • @EnableAutoConfiguration 注解通过 Spring 提供的 @Import 注解导入了 AutoConfigurationImportSelector 类。
  • AutoConfigurationImportSelector 类中的 getCandidateConfigurations 方法会返回所有自动配置类的信息,这些配置信息会被 Spring 容器作为 bean 管理。
  • 自动配置类通常使用 @Conditional 注解进行条件判断,以决定是否需要配置某些 bean。例如,@ConditionalOnClass 会检查指定的类是否存在于类路径中,@ConditionalOnBean 会检查容器中是否有指定的 bean。

开发 RESTful Web 服务常用的注解有哪些?

  • @RestController:是 @Controller@ResponseBody 的组合注解,用于将函数的返回值直接填入 HTTP 响应体中,表示 REST 风格的控制器。
  • @GetMapping:用于处理 GET 请求。
  • @PostMapping:用于处理 POST 请求。
  • @PutMapping:用于处理 PUT 请求。
  • @DeleteMapping:用于处理 DELETE 请求。
  • @RequestParam@PathVariable@PathVariable 用于获取路径参数,@RequestParam 用于获取查询参数。
  • @RequestBody:用于将请求体(可能是 POST, PUT, DELETE, GET 请求)中的 JSON 数据绑定到 Java 对象。

Spring Boot 常用的两种配置文件

  • application.propertiesapplication.yml 是 Spring Boot 应用程序的主要配置文件。
  • YAML 配置文件不支持通过 @PropertySource 注解导入自定义的 YAML 配置。

Spring Boot 常用的读取配置文件的方法有哪些?

  • @Value:用于读取简单的配置信息(不推荐)。
  • @ConfigurationProperties:用于将配置与 bean 绑定。可以通过 @Component 注解或 @EnableConfigurationProperties 注册配置 bean。
  • @PropertySource:用于读取指定的 properties 文件。

Spring Boot 加载配置文件的优先级

  • Spring Boot 加载配置文件的优先级从高到低如下:
    • 外部配置(命令行参数,环境变量)
    • application.propertiesapplication.yml
    • application-{profile}.propertiesapplication-{profile}.yml
    • 内部配置(类路径下的配置文件)

常用的 Bean 映射工具有哪些?

  • 在代码中经常需要将一个数据结构封装成 DO、SDO、DTO、VO 等,常用的 Bean 映射工具有:
    • Spring BeanUtils
    • Apache BeanUtils
    • MapStruct
    • ModelMapper
    • Dozer
    • Orika
    • Jmapper
  • MapStruct 性能较好且使用灵活,是一个不错的选择。

Spring Boot 如何监控系统实际运行状况?

  • 可以使用 Spring Boot Actuator 来监控 Spring Boot 项目的运行状态。
  • 集成 Actuator 后,Spring Boot 应用程序自带了一些开箱即用的 API,可以获取应用程序运行时的内部状态信息。

Spring Boot 如何做请求参数校验?

  • Spring Boot 通过 spring-boot-starter-web 提供请求参数校验功能,包含以下校验注解:

    • @Null:被注释的元素必须为 null
    • @NotNull:被注释的元素必须不为 null
    • @AssertTrue:被注释的元素必须为 true
    • @AssertFalse:被注释的元素必须为 false
    • **@Min(value)**:被注释的元素必须是一个数字,其值必须大于等于指定的最小值。
    • **@Max(value)**:被注释的元素必须是一个数字,其值必须小于等于指定的最大值。
    • **@Size(max=, min=)**:被注释的元素的大小必须在指定的范围内。
    • **@Digits(integer, fraction)**:被注释的元素必须是一个数字,其值必须在可接受的范围内。
    • @Past:被注释的元素必须是一个过去的日期。
    • @Future:被注释的元素必须是一个将来的日期。
    • **@Pattern(regex=, flag=)**:被注释的元素必须符合指定的正则表达式。
  • Hibernate Validator 提供的校验注解:

    • @NotBlank:验证字符串非 null,且长度必须大于 0。
    • @Email:被注释的元素必须是电子邮箱地址。
    • **@Length(min=, max=)**:被注释的字符串的大小必须在指定的范围内。
    • @NotEmpty:被注释的字符串必须非空。
    • **@Range(min=, max=)**:被注释的元素必须在合适的范围内。
  • 验证请求体(RequestBody)

    • 在需要验证的参数上加上 @Valid 注解,如果验证失败,将抛出 MethodArgumentNotValidException,Spring 会将此异常转换为 HTTP Status 400(错误请求)。
  • 验证请求参数(Path Variables 和 Request Parameters)

    • 在类上加上 @Validated 注解,告诉 Spring 去校验方法参数。

如何使用 Spring Boot 实现全局异常处理?

  • 可以使用 @ControllerAdvice@ExceptionHandler 注解处理全局异常。

Spring Boot 中如何实现定时任务?

  • 可以使用 @Scheduled 注解创建定时任务。

Spring

IoC(控制反转)

  • 控制反转(Inversion of Control, IoC) 是指将对象创建的控制权交给 Spring 框架来管理。

    • 控制:对象创建(实例化、管理)的权力。
    • 控制权交给外部环境(Spring 框架、IoC 容器)。
  • IoC 容器会实例化对象并将对象存储起来,在需要使用的时候,直接向 IoC 容器索要对象即可。

  • IoC 容器实际上是一个 Map(key, value),Map 中存放的是各种对象。

解决的问题

  • 对象之间的耦合度高。
  • 对象资源的管理困难。

设计思想

  • 将原本在程序中手动创建对象的控制权交给第三方(如 IoC 容器)。

IoC 容器的实现类

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext
  • ApplicationContext
  • BeanFactory

AOP(面向切面编程)

  • AOP 是 OOP(面向对象编程)的一种延续,旨在将横切关注点(如日志记录、事务管理、权限控制、接口限流等)从核心业务逻辑中分离出来,通过动态代理、字节码操作等方式,实现代码的复用和解耦,提高代码的可维护性和可扩展性。

AOP 关键术语

  • 横切关注点:多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流等)。

  • 切面:对横切关注点进行封装的类,一个切面是一个类,可以定义多个通知,用于实现具体的功能。

  • 连接点(JointPoint):方法调用或者方法执行时的某个特定时刻。

  • 通知(Advice):切面在某个连接点要执行的操作。通知有五种类型:

    • 前置通知(Before)
    • 后置通知(After)
    • 返回通知(AfterReturning)
    • 异常通知(AfterThrowing)
    • 环绕通知(Around)

    image

  • 切点(Pointcut):一个表达式,用于匹配哪些连接点需要被切面所增强。切点通过注解、正则表达式、逻辑运算等方式来定义。

  • 织入:将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。

    • 编译期织入
    • 运行期织入

AOP 的应用场景

  • 日志记录
  • 性能统计
  • 事务管理(@Transactional
  • 权限控制(@PreAuthorize
  • 接口限流
  • 缓存管理

AOP 的实现方式

  • 动态代理

    • 如果被代理对象实现了某个接口,Spring AOP 会使用 JDK Proxy 创建代理对象;如果没有实现接口,则使用 Cglib 生成一个被代理对象的子类,在运行时增强,是代理模式的应用。
  • 字节码操作

    • AspectJ 基于字节码操作,在编译时增强。如果切面较少,差异不大;如果切面较多,AspectJ 的性能更优。

AspectJ

  • AspectJ 是一个面向切面编程的框架,拓展了 Java 语言,定义了 AOP 语法,有专门的编译器生成遵守 Java 字节码规范的 Class 文件。

切面的执行顺序

  • 使用 @Order 注解。
  • 实现 Ordered 接口并重写 getOrder 方法。

Spring Bean 的生命周期

  • Spring 中的 Bean 指的是那些被 IoC 容器管理的对象。

Autowired 和 Resource 的区别

  • @Autowired 默认是通过实例的类型注入的,如果 IoC 容器中存在多个相同类型的类,且不能通过变量名字匹配,将会报错。
  • @Resource 默认是通过变量名字注入的,如果无法通过名称注入,会通过类型注入。可以通过注解的参数 nametype 来设定注入 bean 的名字和类型。
  • 可以使用 @Qualifier 注解配合 @Autowired 来显式指定名称而不依赖变量名称。

Bean 的作用域

  • singleton:默认都是单例模式。
  • prototype:每次获取都是不同的。
  • request:每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP Request 中有效。
  • session:每一次来自新 Session 的请求都会产生一个 bean,该 bean 仅在 HTTP session 内有效。
  • application/global-session:Web 应用在启动的时候创建一个 Bean,该 bean 仅在当前应用启动时间内有效。
  • websocket:每一次 WebSocket 会话产生一个新的 bean。

Bean 的生命周期

  • 实例化:创建 bean 实例。
  • 属性注入:注入 bean 的依赖属性。
  • 初始化:执行 @PostConstructInitializingBean.afterPropertiesSet()、自定义的初始化方法。
  • 销毁:执行 @PreDestroyDisposableBean.destroy()、自定义的销毁方法。

Bean 是线程安全的吗?

  • prototype 作用域下,每次获取都会创建一个新的 bean,不存在资源竞争问题,不存在线程安全问题。
  • singleton 作用域下,IoC 容器只有唯一的 bean 实例,存在资源竞争问题。若这个 bean 有状态(包含可变的成员变量对象),存在线程安全问题。
    • 避免在 bean 中定义可变的成员变量。
    • 可以在类中定义一个 ThreadLocal 变量,将需要的可变成员变量保存在 ThreadLocal 中。

Bean 的生命周期细节

  • 实例化:Spring 通过反射机制创建 bean 的实例。
  • 属性注入:Spring 根据 bean 的定义注入属性,支持构造器注入、Setter 注入和自动注入。
  • 初始化
    • 实现了 BeanFactoryPostProcessor 接口的 bean 会在其他 bean 加载前调用 postProcessBeanFactory 方法。
    • 实现了 InstantiationAwareBeanPostProcessor 接口的 bean 会在实例化之前调用 postProcessBeforeInstantiation 方法。
    • 调用 BeanNameAware 接口的 setBeanName 方法设置 bean 的名称。
    • 调用 BeanFactoryAware 接口的 setBeanFactory 方法设置 BeanFactory
    • 调用 BeanPostProcessorpostProcessBeforeInitialization 方法进行前置处理。
    • 调用 InitializingBean 接口的 afterPropertiesSet 方法。
    • 调用自定义的初始化方法(通过 init-method@PostConstruct 注解定义)。
    • 调用 BeanPostProcessorpostProcessAfterInitialization 方法进行后置处理(包括 AOP 增强)。
  • 销毁
    • 实现了 DisposableBean 接口的 bean,调用 destroy() 方法。
    • 配置了 destroy-method 的 bean 调用自定义的销毁方法,或使用 @PreDestroy 注解定义的销毁方法。

Spring MVC 的核心组件

  • DispatcherServlet:核心的前端控制器,负责接收请求、分发请求并响应客户端。
  • HandlerMapping:处理器映射器,根据 URL 匹配查找能处理的 Handler,并将请求涉及的拦截器和 Handler 一起封装。
  • HandlerAdapter:处理器适配器,适配并执行 Handler。
  • ViewResolver:视图解析器,根据 Handler 返回的逻辑视图,解析并渲染真正的视图。

image

Spring MVC 的请求处理流程

  1. DispatcherServlet 拦截客户端请求。
  2. DispatcherServlet 根据请求信息调用 HandlerMapping,查找匹配的 Handler。
  3. HandlerAdapter 执行匹配到的 Handler。
  4. Handler 处理请求,返回一个 ModelAndView 对象,包含数据模型和视图信息。
  5. ViewResolver 根据逻辑视图查找实际视图。
  6. DispatcherServlet 渲染视图,将 Model 数据传递给视图进行渲染。
  7. 将渲染后的视图返回给客户端。

统一异常处理

  • 使用以下注解:
    • @ControllerAdvice:全局异常处理器,捕获 Controller 中的异常。
    • @ExceptionHandler:用于定义具体的异常处理方法。

Spring 框架用到的设计模式

  • 工厂设计模式:使用 BeanFactoryApplicationContext 创建 bean 对象。
  • 代理设计模式:用于 Spring AOP 功能的实现。
  • 单例设计模式:Spring 中的 bean 默认是单例的。
  • 模板方法设计模式:如 JdbcTemplateRedisTemplate 等模板类。
  • 包装器设计模式:用于连接多个数据库,根据不同客户在每次访问时访问不同的数据库。
  • 观察者设计模式:Spring 事件驱动模型。
  • 适配器模式:Spring AOP 的通知(Advice)使用了适配器,Spring MVC 也使用了适配器适配 Controller。

Spring 事务管理

事务

  • 事务 是逻辑上的一组操作,要么都执行,要么都不执行。事务能否生效取决于数据库引擎是否支持事务(如 MySQL 的 InnoDB 支持事务,而 MyISAM 不支持)。

Spring 事务管理接口

  • PlatformTransactionManager:Spring 事务策略的核心接口,定义了三个方法:
    • TransactionStatus getTransaction(TransactionDefinition definition):获取事务。
    • void commit(TransactionStatus status):提交事务。
    • void rollback(TransactionStatus status):回滚事务。

TransactionDefinition:事务属性

  • 隔离级别:决定一个事务在处理数据时的隔离程度。

    • ISOLATION_DEFAULT:使用数据库的默认隔离级别。
    • ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据,可能导致脏读、幻读或不可重复读。
    • ISOLATION_READ_COMMITTED:允许读取已提交的数据,可以防止脏读,但可能会导致不可重复读和幻读。
    • ISOLATION_REPEATABLE_READ:保证多次读取结果的一致性,可以防止脏读和不可重复读,但可能导致幻读。
    • ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从 ACID 的隔离级别,所有事务依次执行,防止所有并发问题,但性能最差。
  • 传播行为:解决业务层方法之间互相调用的事务问题。

    • PROPAGATION_REQUIRED:默认传播行为,支持当前事务,如果没有就创建一个新事务。
    • PROPAGATION_SUPPORTS:支持当前事务,如果没有就以非事务方式运行。
    • PROPAGATION_MANDATORY:支持当前事务,如果没有则抛出异常。
    • PROPAGATION_REQUIRES_NEW:创建一个新事务,如果当前存在事务则挂起当前事务。
    • PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务则挂起当前事务。
    • PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务则抛出异常。
    • PROPAGATION_NESTED:嵌套事务,在当前事务中嵌套执行,如果当前没有事务则创建一个新事务。
  • 回滚规则:定义哪些异常会导致事务回滚。默认情况下,遇到运行时异常(RuntimeException)和 Error 时回滚,但遇到检查型异常时不会回滚。

  • 只读属性:对于只有读取数据的事务,可以指定为只读事务,数据库会进行一些优化。

  • 事务超时:事务所允许执行的最长时间,超时后自动回滚事务。

TransactionStatus:事务状态

  • **isNewTransaction()**:是否是新的事务。
  • **hasSavepoint()**:是否有保存点。
  • **setRollbackOnly()**:设置为只回滚。
  • **isRollbackOnly()**:是否为只回滚。
  • **isCompleted()**:事务是否已完成。

@Transactional 的使用注意事项

  • @Transactional 注解只作用在 public 方法上,事务才生效,不推荐在接口上使用。
  • 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效。
  • 正确设置 @TransactionalrollbackForpropagation 属性,否则事务可能回滚失败。
  • 使用 @Transactional 的类必须由 Spring 管理,否则事务不生效。
  • 使用的数据库必须支持事务机制。

@Autowired 和 @Resource 的区别

@Autowired@Resource 是 Spring 框架中用于依赖注入的两个常用注解,它们之间有一些重要的区别:

  1. 来源不同
  • **@Autowired**:属于 Spring 框架提供的注解,是 Spring 核心的一部分。
  • **@Resource**:属于 Java EE(JSR-250)标准的注解,由 javax.annotation 包提供。
  1. 注入方式不同
  • **@Autowired**:

    • 默认按类型(by type)注入。如果 Spring 容器中存在多个类型相同的 Bean,可以通过 @Qualifier 注解来指定注入的具体 Bean。
    • 支持构造器、字段、方法的依赖注入。
    • 默认情况下,@Autowired 是必须找到一个匹配的 Bean 的。如果容器中没有找到对应的 Bean,Spring 会抛出 NoSuchBeanDefinitionException。可以通过设置 required 属性为 false 来允许非必须注入。
      @Autowired(required = false)
    private MyBean myBean;

    - **`@Resource`**:

    - 默认按名称(by name)注入。它首先会根据名称查找对应的 Bean,如果找不到,再根据类型查找。

    - 支持名称注入(`name` 属性)和类型注入(`type` 属性),但不能和 `@Qualifier` 一样在同一个注解中同时使用。

    - 如果没有指定 `name` 或 `type`,则默认按照字段名称进行注入。

    ```java
    @Resource(name = "myBean")
    private MyBean myBean;
  1. 默认行为
  • **@Autowired**:默认是按类型注入。如果你想要按名称注入,可以结合 @Qualifier 注解使用。
  • **@Resource**:默认是按名称注入。如果找不到对应名称的 Bean,再按类型注入。
  1. 支持的注入类型
  • **@Autowired**:支持构造函数注入、方法注入和字段注入。
  • **@Resource**:通常用于字段和 setter 方法注入,不支持构造函数注入。
  1. 使用场景
  • **@Autowired**:在需要使用 Spring 特有的功能,如 @Qualifier@Primary 等时,使用 @Autowired 更合适。
  • **@Resource**:如果需要确保与 Java EE 标准的兼容性,或者项目中同时使用了 Spring 和 Java EE 框架,使用 @Resource 更为合适。
  1. 优先级
  • @Resource 注解中,如果同时指定了 nametype 属性,容器会优先根据 name 属性查找 Bean。
// 使用 @Autowired
@Autowired
private MyService myService;

@Autowired
@Qualifier("myServiceImpl")
private MyService myServiceImpl;

// 使用 @Resource
@Resource
private MyService myService;

@Resource(name = "myServiceImpl")
private MyService myServiceImpl;

总结

  • @Autowired 更适合使用 Spring 框架特性并依赖 Spring 提供的自动装配功能。
  • @Resource 更符合 Java EE 标准,通常在需要与标准兼容的场景下使用。

Spring Bean

Bean 的作用域有哪些?

  • singleton:IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  • prototype:每次获取都会创建一个新的 bean 实例。即连续 getBean() 两次,得到的是不同的 Bean 实例。
  • request(仅 Web 应用可用):每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP 请求中有效。
  • session(仅 Web 应用可用):每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
  • application/global-session(仅 Web 应用可用):Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
  • websocket(仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。

如何配置 Bean 的作用域?

  • XML 方式

    <bean id="..." class="..." scope="singleton"></bean>
  • 注解方式

    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Bean 的生命周期

  • 实例化 Bean:通过反射机制创建 bean 实例。
  • Bean 内部的属性赋值:注入 bean 的依赖属性。
  • 初始化:执行 @PostConstructInitializingBean.afterPropertiesSet()、自定义的初始化方法。
  • 销毁:执行 @PreDestroyDisposableBean.destroy()、自定义的销毁方法。