32 道 Spring 常见面试题及详细解析

Spring 框架理解

什么是 Spring 框架?

Spring 是一个轻量级的开源 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。

我们一般所说的 Spring 框架指的是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Spring 支持 IoC (Inverse of Control:控制反转) 和 AOP(Aspect Oriented Programming:面向切面编程),可以很方便地对数据库进行访问;可以与第三方框架(如 Struts2、Hibernate、MyBatis 等)进行整合;可以方便地进行 Web 开发,提供 RESTful 服务;可以方便地进行单元测试和集成测试等等。

Image
Spring 的核心思想就是不重新造轮子,开箱即用!

Spring 的名字翻译过来就是春天的意思,寓意着:给 Java 开发者带来了春天! 😜

🤐 说句题外话:一门编程语言的流行程度,往往取决于其杀手级应用的出现,而 Spring 框架正是 Java 生态中的杀手级框架。

Spring 提供的核心功能主要是 IoC 和 AOP。学习 Spring,一定要好好理解 IoC 和 AOP 这两个重要的概念!

Spring 框架包含哪些模块?

Spring 4.x 版本

Image

Spring 5.x 版本

Image

在 Spring5.x 中,Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。

Spring 各个模块之间的依赖关系,如下图所示:

Image

核心容器

这是 Spring 框架最核心的模块, 它提供了 Spring 框架的基础功能, Spring 其他所有的功能都需要依赖于该模块,从依赖关系图中我们可以看到这一点。

  • spring-core:Spring 框架基本的核心工具类。
  • spring-beans:提供对 Bean 的创建、配置和管理等功能的支持。
  • spring-context:提供对国际化、事件传播、资源加载等功能的支持。
  • spring-expression:提供对 Spring 表达式语言(SpEL)的支持,只依赖于 core 模块,可以独立使用。

AOP

  • spring-aspects:该模块为与 AspectJ 的集成提供支持。
  • spring-aop:提供了面向切面的编程实现。
  • spring-instrument: 提供一些类级的工具支持和ClassLoader级的实现,用于服务器。该模块提供为 JVM 添加代理(agent)的功能。 具体来讲,它提供了一个支持 Spring 的 AOP 的 WeavingAgent,可以将 Spring 的 AspectJ 切面应用于 Tomcat 启动的 Web 应用。 其实,instrument 包的 WeavingAgent 还有其他应用,比如性能监控等。 不过,该模块的使用场景非常有限,使用起来也比较复杂。

数据访问/集成

  • spring-jdbc:提供了一个 JDBC 的抽象层,消除了烦琐的 JDBC 编码和数据库厂商特有的错误代码解析,用于简化 JDBC。
  • spring-tx:提供对事务的支持。
  • spring-orm:为流行的对象关系映射(ORM)API 提供集成层。包括 JPA、JDO、Hibernate 和 iBatis。
  • spring-oxm:提供了一个对 Object/XML映射实现的抽象层,Object/XML 映射实现包括 JAXB、Castor、XMLBeans、JiBX 和 XStream。
  • spring-jms:指 Java 消息服务,提供了一种创建、发送、接收消息的标准方法。 Spring 框架对 JMS 提供了全面的支持。从 Spring Framework 4.1 开始,它还提供与 spring-messaging 模块的集成。

Web

  • spring-web:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器以及 Web 应用上下文。
  • spring-webmvc:包含 Spring MVC 框架的相关的所有类。 Spring MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。
  • spring-websocket:Spring4.0 以后新增的模块,它提供了 WebSocket 和 SockJS 的实现,以及对 STOMP 的支持。
  • spring-webflux:是一个新的非阻塞函数式 Reactive Web 框架,可以用来构建异步的、非阻塞的、事件驱动的服务,在伸缩性方面表现出色。 它与 Spring MVC 不同,它不需要 Servlet API,完全异步和非阻塞,并通过 Reactor 项目实现了 Reactive Streams 规范。

消息

spring-messaging 模块是从 Spring4.0 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。

Spring Test

Spring 团队提倡测试驱动开发(TDD)。有了控制反转 (IoC) 的帮助,单元测试和集成测试变得更简单。

Spring 的测试模块对 JUnit 或 TestNG 测试框架有很好的支持。

Spring、Spring MVC 和 Spring Boot 之间的关系

很多小伙伴搞不清楚 Spring、Spring MVC 和 Spring Boot 之间的关系,这里简单地说一下:

Spring 包含了多个功能模块,其中最重要的是 Spring-Core 模块,主要提供 IoC 依赖注入功能。Spring MVC 属于 SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 里面,它主要是为了简化我们日常 Web 开发。

下图对应的是 Spring4.x 版本。而在最新的 Spring5.x 版本中,Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。

Image

SpringMVC 是 Spring 中的一个很重要的模块,它实现了 Web MVC 的设计模式,能够帮助我们快速开发 Web 应用。MVC 是 Model View Controller 的缩写,它将应用程序划分为三个核心模块:模型、视图和控制器,各司其职。

Image
在使用 Spring 进行开发的时候,需要配置大量的 XML 文件,随着项目的越来越大,需要将 XML 配置文件拆分成多个配置文件,导致配置文件的管理非常复杂,给开发和维护都带来一定难度。 为了解决 Spring 的配置复杂问题,Spring Boot 应运而生。

Spring Boot 是在 Spring 的基础上搭建的,用于简化 Spring 应用的初始搭建以及开发过程。它可以自动配置 Spring 的各种组件,默认采用约定大于配置的原则,简化了 Spring 的配置流程。

需要注意的是,Spring Boot 只是简化了配置,如果你需要构建 MVC 架构的 Web 应用程序,你仍然需要使用 Spring MVC 作为你的基础框架,只是说 Spring Boot 可以帮助你简化 Spring MVC 的配置,真正做到开箱即用!

Spring IoC 理解

什么是 Spring IoC ?

控制反转(Inversion of Control,缩写为 IoC),是面向对象编程中的一种设计原则,用来降低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称 DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。所以,控制反转是,关于一个对象如何获取他所依赖的对象的引用,这个责任的反转。

为什么叫做控制反转呢?

  • **谁控制谁?**当然是 IoC 容器控制了对象;
  • **控制了什么?**那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  • **为何是反转?**因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

Image
IoC 是一个原则,而不是一个具体的技术实现。IoC 的思想最核心的地方在于,资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。

  • 资源集中管理,实现资源的可配置和易管理。
  • 降低了使用资源双方的依赖程度,也就是我们说的耦合度。

控制反转具体怎么实现呢?

IoC 的实现方式有很多种,其中最常见的方式是 依赖注入(DI)。依赖注入是指将对象依赖的其他对象通过构造函数、setter 方法或者接口注入到对象中。

在实际项目中,一个 Service 类可能依赖于多个 DAO 类,如果我们需要实例化这个 Service 类,就需要了解所有依赖的 DAO 类的构造函数,这显然非常麻烦。而使用 IoC,我们只需要在配置文件中配置好 Service 类和 DAO 类的依赖关系,然后在需要使用 Service 类的地方直接注入即可,大大简化了开发的复杂度。

在 Spring 中, IoC 容器是实现 IoC 的载体,它可以自动管理对象之间的依赖关系。IoC 容器就相当于一个 Map(key,value),其中 key 是 Bean 的名称,value 是 Bean 实例。

Spring IoC 容器就像是一个工厂,当我们需要创建一个对象的时候,只需要配置好配置文件/注解,然后调用 getBean() 方法,就可以获取到所需的对象,而不需要关心对象的创建过程。

在早期,我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。

什么是 Spring Bean?

简单来说,Bean 代指那些被 IoC 容器所管理的对象。

我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。

<!-- 通过构造函数的 value 属性来指定参数 -->  
<bean id="..." class="...">  
   <constructor-arg value="..."/>  
</bean>  

下图简单地描述了 IoC 容器是如何使用配置元数据来管理对象。

Image
org.springframework.beansorg.springframework.context 包是 Spring 框架 IoC 实现的基础,如果要深入研究 IoC 相关的源码,可以从这两个包入手。

Spring 中有哪些方式来配置 Bean?

  • 基于 XML 配置: 在 XML 文件中通过 <bean> 标签来配置 Bean。
  • 基于注解配置: 使用注解来标识 Bean,例如 @Component@Service@Controller@Repository 等。
  • 基于 Java 配置: 使用 Java 类来配置 Bean,例如 @Configuration@Bean 等。

声明 Bean 的注解有哪些?

  • @Component: 组件,没有明确的角色
  • @Service: 在业务逻辑层使用(service 层)
  • @Repository: 在数据访问层使用(dao 层)
  • @Controller: 在展现层使用,控制器的声明(C)

@Component@Bean 的区别是什么?

  • @Component 注解作用于类,而 @Bean 注解作用于方法。
  • @Component 通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。 @Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。
  • @Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring 容器时,则只能通过 @Bean 来实现。

@Bean 使用示例:

@Configuration  
public class AppConfig {  
    @Bean  
    public TransferService transferService() {  
        return new TransferServiceImpl();  
    }  
}  

上面的代码相当于下面的 xml 配置

<beans>  
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>  
</beans>  

下面这个例子是 @Component 无法实现的。

@Bean  
public OneService getService(status) {  
    case (status) {  
        case 1:  
            return new serviceImpl1();  
        case 2:  
            return new serviceImpl2();  
        case 3:  
            return new serviceImpl3();  
    }  
}  

注入 Bean 的注解有哪些?

Spring 提供了以下三种注解来注入 Bean,@Autowired@Resource@Inject

注解来源
@Autowiredorg.springframework.beans.factorySpring 2.5+
@Resourcejavax.annotationJSR-250
@Injectjavax.injectJSR-330

@Autowired@Resource 都是用来装配 bean 的,都可以写在字段上,或者写在 setter 方法上。

@Autowired@Resource 的区别是什么?

@Autowired 是 Spring 提供的注解,默认按类型装配(这个注解是属业按类型装配注入的,要是按名称装配的话,需要用 @Qualifier 注解)。

会出现什么问题呢? 如果我们想使用接口的多个实现类就会出现问题,因为 byType 的方式会找到多个符合条件的选择,导致 Spring 无法判断要注入哪个实现类。

这种情况下,注入方式会变成 byName 的方式,也就是默认会将属性名作为 Bean 的名称去匹配。

例如,下面这段代码,默认情况下,smsService 就是默认的 Bean 的名称:

@Autowired  
private SmsService smsService;  

如果 SmsService 接口有两个实现类,分别是 SmsServiceImpl1SmsServiceImpl2,并且这两个实现类都交给 Spring 管理了:

// 报错,byName 和 byType 都无法匹配到对应的 bean  
@Autowired  
private SmsService smsService;  
// 正确注入 SmsServiceImpl1 对应的 bean  
@Autowired  
private SmsService smsServiceImpl1;  
// 正确注入 SmsServiceImpl1 对应的 bean  
// smsServiceImpl1 就是我们上面说的默认 Bean 的名称  
@Autowired  
@Qualifier(value = "smsServiceImpl1")  
private SmsService smsService;  

建议大家最好是明确地指定 Bean 的名称,防止出错!

@Resource 是 JDK 提供的注解,默认按照 Bean 的名称进行装配,名称可以通过 name 属性进行指定,如果没有指定 name 属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在 setter 方法上默认取属性名进行装配。当找不到与名称匹配的 bean 时才按照类型进行装配。但是需要注意的是,如果 name 属性一旦指定,就只会按照名称进行装配。

@Resource 有两个重要的属性:nametype

public @interface Resource {  
    String name() default "";  
    Class<?> type() default Object.class;  
}  

name 属性指定 bean 的名称,type 属性指定 bean 的类型。Spring 将 @Resource 注解的 name 属性解析为 bean 的名称,而 type 属性则解析为 bean 的类型。所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。如果既不指定 name 也不指定 type 属性,这时将通过反射机制使用 byName 自动注入策略。

// 报错,byName 和 byType 都无法匹配到对应的 bean  
@Resource  
private SmsService smsService;  
// 正确注入 SmsServiceImpl1 对应的 bean  
@Resource  
private SmsService smsServiceImpl1;  
// 正确注入 SmsServiceImpl1 对应的 bean (推荐使用这种方式)  
@Resource(name = "smsServiceImpl1")  
private SmsService smsService;  

Spring 中 Bean 的作用域有哪些?

Spring 中 Bean 的作用域通常有以下几种:

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
  • application : bean 被定义为在 ServletContext 的生命周期中复用一个单例对象。
  • websocket : 每一个 WebSocket 都会创建一个新的 bean。

如何配置 bean 的作用域呢?

XML 方式:

<bean id="..." class="..." scope="singleton"></bean>  

注解方式:

@Bean  
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)  
public Person personPrototype() {  
    return new Person();  
}  

单例 Bean 是线程安全的吗?

大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程安全问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在并发问题。

常见的有两种解决办法:

  1. 在 bean 对象中尽量避免定义可变的成员变量(不太现实)。
  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

不过,大部分 bean 实际都是无状态(没有实例变量)的(比如 DAO 类、Service 类),这种情况下, bean 其实是线程安全的。

你能说一下 Bean 的生命周期吗?

以下内容参考了:https://yemengying.com/2016/07/14/spring-bean-life-cycle/https://www.cnblogs.com/zrtqsk/p/3735273.html 这两篇博客。

  1. Spring 容器从 XML 文件中读取 Bean 的定义,并实例化 Bean。
  2. Spring 容器根据 Bean 的定义填充所有的属性。
  3. 如果 Bean 实现了 BeanNameAware 接口,Spring 容器将 Bean 的 ID 传递给 setBeanName() 方法。
  4. 如果 Bean 实现了 BeanClassLoaderAware 接口,Spring 容器将 ClassLoader 对象的实例传递给 setBeanClassLoader() 方法。
  5. 如果 Bean 实现了 BeanFactoryAware 接口, Spring 容器将 BeanFactory 对象的实例传递给 setBeanFactory() 方法。
  6. 与上面的类似,如果 Bean 实现了其他 *.Aware 接口,Spring 容器将调用相应的方法。
  7. 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessBeforeInitialization() 方法
  8. 如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet() 方法。