为了账号安全,请及时绑定邮箱和手机立即绑定

必知必会面试题之 Spring 基础

标签:
Spring

title: 必知必会面试题之 Spring 基础
date: 2021-03-10
updated: 2021-03-10
categories:

  • Spring
    tags:
  • Spring
  • 面试

不定期更新……



基础知识

Spring 的优点

> 考察点:考查对 Spring 框架的熟悉程度。
>
> 小贴士:答上来这个问题,说明你对 Spring 框架 有一定的了解。
>
> 难易度:低。

  1. 方便解耦,简化开发。
  2. 提供了 BOP(面向 Bean 编程)、IoC(依赖控制反转)、AOP(面向切面编程)等优秀的特性。
  3. 声明式事务管理(TransactionManager)
  4. 强大的工具类(如:JdbcTemplate)
  5. 易于集成其他开源框架
  6. 强大的开源生态(SpringBoot、SpringCloud、SpringCloudAlibaba)

Spring 有哪些核心模块

> 考察点:考查对 Spring 框架模块的了解。
>
> 小贴士:答上来这个问题,说明了解了 Spring 架构的拆分和设计,如果聊到自己的项目架构时,可以借鉴。
>
> 难易度:低。

Spring 目前有 21 个模块:

  • spring-aop:核心模块,提供 AOP 的实现。
  • spring-aspects:集成 AspectJ 框架。
  • spring-beans:核心模块,提供 Bean 的管理和 BeanFactory 的实现。
  • spring-context-indexer:Spring 5 新增模块,通过索引(编译阶段创建对象列表)来提高启动速度。
  • spring-context-support:支持整合第三方库到上下文中,如:EhCache、JCache、Quartz 等。
  • spring-context:核心模块,基于 core 和 beans 模块能力,实现上下文管理如。如:ApplicationContext。
  • spring-core:核心模块,提供框架核心能力和特性,包括控制反转和依赖注入。
  • spring-expression:提供表达式支持,是 EL 表达式在 Spring 框架中的应用和扩展。
  • spring-instrument:AOP 的增强模块,提供类植入支持和类加载器的实现。
  • spring-jcl:Spring 5 新增模块,提供通用日志的功能。
  • spring-jdbc:提供了 JDBC 的抽象与封装,简化 JDBC 连接方式。如:JdbcTemplate。
  • spring-jms:集成 JMS 服务,用于消息的传递。
  • spring-messaging:Spring 4 新增模块,提供消息传递结构和协议的支持。
  • spring-orm:提供 ORM 框架支持,支持创建对象关系映射。
  • spring-oxm:提供 Object 和 XML 的映射功能。
  • spring-r2dbc:Spring 5 新增模块,提供完全反应式非阻塞的 API 与数据库交互,支持 H2、MariaDB、SQL Server、MySQL、jasync-sql MySQL、Postgres。
  • spring-test:提供了测试功能,支持 JUnit 等测试组件。
  • spring-tx:提供事务的支持。
  • spring-web:提供基本的 Web 开发相关功能的集成。
  • spring-webflux:非堵塞函数式 Reactive Web 框架,用来建立异步非阻塞的基于事件驱动的服务。
  • spring-webmvc:Web-Servlet 框架,包含用于应用程序的 Spring MVC 和 REST Web Services 实现。
  • spring-websocket:Spring 4 新增模块,实现双工异步通讯协议,实现了 WebSocket 和 SocketJS,提供 Socket 通信和 web 端的推送功能。

Spring 用到了哪些设计模式

> 考察点:考查对 Spring 源码的理解和设计模式。
>
> 小贴士:答上来这个问题,说明了解了 Spring 中的设计模式,继续问下去可能会让举些例子,比如具体的实现类。
>
> 难易度:低。

  • 单例模式:Bean 默认是单例模式。如:FactoryBean。
  • 工厂模式:BeanFactory 简单工厂,用来创建对象的实例。
  • 代理模式:JDK 动态代理、CGLIB 字节码生成技术。
  • 模板方法:RestTemplate、JdbcTemplate。
  • 观察者模式:ApplicationListener。

Spring 的事件有哪些

> 考察点:考查对 Spring Event 的了解,以及 Spring 的运行机制。
>
> 小贴士:答上来这个问题,说明了解了 Spring 事件机制,继续问下去可能会问一些扩展问题,比如和 Java 中的事件的区别。
>
> 难易度:中

  1. 上下文开始事件(ContextStartedEvent)

    调用 ConfigurableApplicationContext 的 start() 方法(继承自 Lifecycle),启动容器时触发该事件。

  2. 上下文更新事件(ContextRefreshedEvent)

    调用 ConfigurableApplicationContext 接口中的 refresh() 方法时,更新容器时触发该事件。

  3. 上下文停止事件(ContextStoppedEvent)

    当容器调用 ConfigurableApplicationContext 的 stop() 方法(继承自 Lifecycle),停止容器时触发该事件。

  4. 上下文关闭事件(ContextClosedEvent)

    当 ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例 Bean 都会被销毁。

  5. 请求处理事件(RequestHandledEvent)

    当一个 http 请求(request)结束触发该事件。如果一个 Bean 实现了 ApplicationListener 接口,当一个 ApplicationEvent 被发布以后,就会自动通知这个 Bean。

Spring 配置方式

> 考察点:考查对 Spring 框架的了解。
>
> 小贴士:答上来这个问题,说明了解了 Spring 的配置机制,继续问下去可能会问配置读取进来后是如何实现的,能答上具体的实现类最佳。
>
> 难易度:低

  • 基于注解的配置(推荐)
  • 基于 Java 代码的配置(推荐)
  • XML 配置文件(不推荐,因 XML 标签格式过于繁琐,业内掀起了去 XML 化的“浪潮”)

Spring 的自动装配

> 考察点:考查对 Spring 源码的理解和设计模式。
>
> 小贴士:答上来这个问题,说明了解了 Spring 自动装配策略,继续问下去可能会让举例说明。
>
> 难易度:中

Spring 的自动装配是指由 Spring 根据不同场景自动完成 Bean 的装配的方式。

在 Spring 中提供了 4种自动装配策略:

  • AUTOWIRE_NO:无需自动装配(默认策略)
  • AUTOWIRE_BY_NAME:按名称自动装配,例如:@Resource、@Qualifier 注解。
  • AUTOWIRE_BY_TYPE:按类型自动装配,例如:@Autowired 注解。
  • AUTOWIRE_CONSTRUCTOR:按构造器自动装配,例如:在构造器或构造器入参上添加 @Autowired 注解。

此外,还有 Spring 3.0 以后被废弃的一种装配策略:AUTOWIRE_AUTODETECT(自动探测装配)。自动探测装配会根据不同情况选择不同的装配方式:如果存在默认构造器,则使用构造器装配;否则,使用类型装配。

> 笔者猜测有两个原因导致该策略的废弃:
>
> 1. 不符合设计初衷:“如果条件 A 走 X,如果条件 B 走 Y”,这种方式将选择策略的任务交给了上层应用,不符合 Spring 使开发者更专注于业务的初衷。
> 2. 丰富的注解:Spring 提供了越来越丰富的注解来解决容器对象管理,上层应用根据需求选择合适注解即可。

Spring 事务的实现原理

> 考察点:考查对 Spring 事务实现机制的了解。
>
> 小贴士:答上来这个问题,说明了解了事务的底层实现机制,继续问下去可能会扯到数据库相关知识。
>
> 难易度:低

Spring 事务是基于数据库的事务封装实现的,最终事务的处理都是由数据库层的事务提交和回滚实现的。

Spring 事务传播机制(行为)

> 考察点:考查对 Spring 事务传播机制的了解。
>
> 小贴士:答上来这个问题,说明了解了事务传播机制,继续问下去可能会问在实际项目中的运用。
>
> 难易度:低

Spring 的事务传播机制,实际上是对数据库事务行为的封装,初衷是为上层应用提供一定规则的事务处理方式,简化开发。

Spring 的 TransactionDefinition 接口中定义了 7 种事务传播机制。

  1. PROPAGATION_REQUIRED:(默认)如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务。
  2. PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
  3. PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  4. PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
  5. PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
  6. PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  7. PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按 REQUIRED 属性执行。

Spring 事务隔离级别

> 考察点:考查对 Spring 事务隔离级别的了解。
>
> 小贴士:答上来这个问题,说明了解了事务隔离级别,继续问下去可能会问数据库相关知识。
>
> 难易度:低

由于 Spring 事务是基于底层数据库事务实现的,因此,隔离级别也是一样的。

Spring 的 TransactionDefinition 接口中定义了 5 种事务隔离级别。

  1. ISOLATION_DEFAULT:(默认)用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
  2. ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
  3. ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
  4. ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
  5. ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。

Bean

Bean 的作用域

> 考察点:考查对 Bean 作用域的了解。
>
> 小贴士:答上来这个问题,说明了解了 Bean 的作用域,继续问下去可能会问实际项目中的运用。
>
> 难易度:低

  • singleton:默认作用域,每个 Bean 在单个 IOC 容器中只存在一个实例。
  • prototype:一个 Bean 存在多个实例。
  • request:一个 Http 请求对应一个 Bean 实例,作用域范围为单次请求的生命周期,随着请求结束而销毁。
  • session:一个 Http Session 对应一个 Bean 实例。作用域范围为当前 Session 的生命周期,随着 Session 会话结束而销毁。
  • global-session:一个全局的 Http Session 对应一个 Bean 实例。

Bean 是线程安全的吗

> 考察点:考查对 Bean 实现机制的了解。
>
> 小贴士:答上来这个问题,说明了解了 Bean 的线程安全性,继续问下去会问如何保证线程安全。
>
> 难易度:低

如果 Bean 是单例的,所有线程共享同一个 Bean 实例,存在资源竞争,此时 Bean 不是线程安全的。

如果 Bean 是单例无状态的,虽然所有线程共享同一个 Bean 实例,但线程不会对 Bean 的资源执行查询以外的操作,不存在资源竞争,此时 Bean 是线程安全的。

如果 Bean 是多例的,每个线程都持有一个独有的 Bean 实例,不存在资源竞争,此时 Bean 是线程安全的。

如何保证 Bean 的线程安全

> 考察点:考查 Bean 线程安全的解决方案。
>
> 小贴士:答上来这个问题,说明了解了 Bean 的线程安全解决方法,继续问下去可能会问实际项目中的运用。
>
> 难易度:中

  • 将 Bean 的作用域改为 prototype。

  • 使用 java 中提供的 ThreadLocal。

  • 通过 Lock 加锁保证。

Bean 的生命周期

> 考察点:考查 Bean 的生命周期。
>
> 小贴士:答上来这个问题,说明了解了 Bean 的生命周期,一般不会继续问下去。
>
> 难易度:低

  1. 实例化:Bean 的实例化。
  2. 属性填充:将 Bean 所需的值和引用填充到 Bean 对应的属性。
  3. 预加载:根据 Bean 实现的接口,调用对应的方法。
    • 若实现 BeanNameAware接口,则调用 setBeanName() 方法,传入 Bean 的名称;
    • 若实现 BeanFactoryAware 接口,则调用 setBeanFactory() 方法,传入 BeanFactory 实例;
    • 若实现 ApplicationContextAware 接口,则调用 setApplicationContext() 方法,传入 Bean 所在的应用上下文;
    • 若实现 BeanPostProcessor 接口,则调用 postProcessBeforeInitialization() 方法,传入 Bean 和 Bean 的名称;
    • 若实现 InitializingBean 接口或声明了初始化方法,则调用 afterPropertiesSet() 方法;
    • 若实现了 BeanPostProcessor 接口,则调用 postProcessAfterInitialization() 方法,传入 Bean 和 Bean 的名称。
  4. 加载完成:将 Bean 成功完成加载,可正常调用。
  5. 销毁:调用 DisposableBean 的 destroy() 接口方法,销毁 Bean。

BeanFactory 和 FactoryBean 的区别

> 考察点:考查 Spring 中的核心类。
>
> 小贴士:答上来这个问题,说明了解了 BeanFactory 和 FactoryBean 的特性,一般不会继续问下去。
>
> 难易度:低

BeanFactory 是实现 IOC 容器的核心接口,提供了实例化对象、获取对象的功能。

FactoryBean 是实现 Bean 的接口,提供了工厂 Bean 的实例化规范。

BeanFactory 和 ApplicationContext 的区别

> 考察点:考查 Spring 中的核心类。
>
> 小贴士:答上来这个问题,说明了解了 BeanFactory 和 ApplicationContext 的特性,继续问下去可能 ApplicationContext 是重点。
>
> 难易度:低

  1. BeanFactory 是实现 IOC 容器的基本接口;ApplicationContext 继承并扩展了 BeanFactory。
  2. BeanFactory 是懒加载的,每次获取对象时创建对象;ApplicationContext 是预加载的,每次容器启动时就回创建所有对象。
  3. BeanFactory 提供了实例化对象和获取对象的功能。ApplicationContext 提供了更丰富的功能,如:国际化(MessageSource)、访问资源、载入多个上下文、消息发送响应机制(ApplicationEvent)。
  4. BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用,但两者之间的区别是:BeanFactory 需要手动注册,而 ApplicationContext 则是自动注册。

IOC

什么是 IOC

> 考察点:考查 IOC 的概念。
>
> 小贴士:答上来这个问题,说明了解了 IOC 的作用,继续问下去可能会问 IOC 的实现原理。
>
> 难易度:低

IOC 全称为 Inversion of Control,意为控制反转。控制反转就是指将对象的控制权不再由具体实现代码掌控,而是由容器来控制。

在 Spring 中使用 IOC 将对象的创建、管理、装配、配置以及生命周期统一交由容器管理。

Spring 中 IOC 的实现原理

> 考察点:考查 IC 的实现原理。
>
> 小贴士:答上来这个问题,说明了解了 IOC 的实现原理,继续问下去可能会问循环依赖的问题。
>
> 难易度:中

工厂模式 + 反射。

实现基础的 IOC 分为两个步骤:

  1. 加载配置文件,解析成 BeanDefinition 放在 Map 里。
  2. 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法,完成依赖注入。

Spring 中 IOC 的实现形式

> 考察点:考查 Spring 中 IOC 的实现方式。
>
> 小贴士:答上来这个问题,说明了解了 IOC 在 Spring 中的具体实现方式,继续问下去可能会问依赖注入和依赖查找的实现流程。
>
> 难易度:低

依赖注入(Dependency Injection)和依赖查找(Dependency Search)。

  • 依赖注入:应用不再手动管理所需资源,而是由容器动态地将所需资源注入到应用所需的类中。

  • 依赖查找:应用不再手动加载所需资源,而是由加载器动态地将所需资源查询并加载到容器中。

Spring 中依赖注入有哪几种方法

> 考察点:考查依赖注入的实现方式。
>
> 小贴士:答上来这个问题,说明了解了依赖注入的方法,继续问下去可能会问依赖注入的实现流程。
>
> 难易度:低

  1. 构造器注入
  2. 使用注解 @Autowird 注入
  3. 使用 Setter 方法注入

为什么 Spring 推荐使用构造器注入?

> 考察点:考查构造器注入的优缺点。
>
> 小贴士:答上来这个问题,说明了解了构造器注入,一般不会继续问下去。
>
> 难易度:中

引用 Spring 官方文档的描述:

> The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.

主要提到以下几点好处:

  • 组件不可变:是指 final 关键字,不可变的构造器可以保证使用者每次调用都能够得到稳定可靠的结果。在 IDEA 中如果使用 @Autowired 注解,会提示你使用 final 类型的构造器替代该注解。
  • 依赖不为空:构造器提供了固定的传入参数,因此可以保证返回的构造器一定是具有一定参数的构造器,可以避免依赖为空导致的空指针报错。
  • 完全初始化:构造器能够确保装载了所需依赖,保证了返回的对象是完全初始化好了的对象,不会缺少属性。

但要注意的是:如果对象之间存在循环依赖,请避免使用构造器注入,防止造成循环依赖报错

Spring 如何解决循环依赖问题

> 考察点:考查 Spring 中循环依赖的解决方案。
>
> 小贴士:答上来这个问题,说明了解了循环依赖的解决方案,一般不会继续问下去。
>
> 难易度:中

在 Spring 解决循环依赖是有前置条件的:

  1. 出现循环依赖的Bean必须要是单例
  2. 依赖注入的方式不能全是构造器注入的方式

那么,Spring 如何解决循环依赖问题的呢?这个问题有些抽象,下面举例说明。

假设有两个类 A、B,其中 A 依赖 B,B 依赖 A。

在 Spring 中,加载过程如下:

  1. 首先根据 Spring 自然排序规则,先去获取 A 对象实例,第一次获取会发现缓存中没有 A 实例对象,返回 null;
  2. 由于未获取到 A 对象实例,进行创建 A 对象实例
  3. 创建 A 对象实例时,发现 A 对象依赖 B 对象,循环获取二级缓存中的对象引用,尝试获取 B 对象实例来注入到 A 对象实例中;
  4. 由于缓存中没有 B 对象实例,所以会创建 B 对象实例
  5. 此时,A 对象实例获取得到 B 对象实例(已实例化,但未注入属性信息,未初始化),A 对象实例加载完成;
  6. 创建 B 对象实例时,发现 B 对象依赖 A 对象,获取 A 对象实例来注入到 B 对象实例中;
  7. 此时,B 对象实例加载完成;

AOP

什么是AOP

> 考察点:考查 AOP 的概念。
>
> 小贴士:答上来这个问题,说明了解了 AOP 的概念,继续问下去可能会问实现原理。
>
> 难易度:低

AOP 全称 Aspect Oriented Programming,即面向切面编程。用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect)。

AOP 可以减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

适用场景包括:权限认证、日志采集、事务处理等。

Spring 中 AOP 是如何实现的

> 考察点:考查 AOP 的实现原理。
>
> 小贴士:答上来这个问题,说明了解了 AOP 的实现原理,继续问下去可能会问静态代理和动态代理。
>
> 难易度:低

基于代理实现的,包括静态代理和动态代理。

什么是静态代理

> 考察点:考查静态代理的概念。
>
> 小贴士:答上来这个问题,说明了解了静态代理的概念,一般不会继续问下去,动态代理才是重点。
>
> 难易度:低

静态代理是指在编译阶段生成代理对象的方法,会在编译阶段将 AspectJ(切面)代码嵌入到 Java 字节码当中。

什么是动态代理

> 考察点:考查动态代理的概念。
>
> 小贴士:答上来这个问题,说明了解了静态代理的概念,继续问下去可能会问两种动态代理的区别。
>
> 难易度:低

动态代理是指在运行阶段生成代理对象的方法,会在运行阶段动态生成 AOP 代理对象,可以在特定的切点做增强处理,并回到原对象的方法。

JDK 动态代理与 CGLIB 动态代理的区别

> 考察点:考查 JDK 和 CGLIB 的实现机制。
>
> 小贴士:答上来这个问题,说明了解了 JDK 和 CGLIB 的实现机制,一般不会继续问下去。
>
> 难易度:中

JDK 动态代理是基于接口的代理,通过 Java 提供的 InvocationHandler 接口Proxy 类实现。分为两个步骤:

  1. InvocationHandler 通过 invoke() 方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起,生成代理类。
  2. Proxy 利用 InvocationHandler 生成的代理类,动态创建一个符合该代理类的实例,生成目标类的代理对象。

CGLIB(Code Generation Library)是一个代码生成的类库,不需要代理类实现 InvocationHandler 接口。通过在运行时动态生成目标类的一个子类对象,覆盖其中特定方法并添加增强代码,从而实现 AOP。

CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消