Java 热门面试题200道之Spring 框架(19 题)
(1)说说 Spring 启动过程?
Spring 容器的启动过程可以概括为以下几个核心步骤:
1. 初始化容器:创建 ApplicationContext 实例(如 AnnotationConfigApplicationContext),这会触发容器启动。
2. 加载与解析配置:容器扫描指定的配置源(如配置类@Configuration),解析 @ComponentScan 等注解,将 Bean 的定义信息(BeanDefinition)收集并注册到 BeanFactory 中。
3. 执行 BeanFactoryPostProcessor:调用所有 BeanFactoryPostProcessor 对 BeanDefinition 进行修改或扩展。这是容器的一个重要扩展点。
4. 实例化 Bean:容器(非懒加载的单例)通过反射创建 Bean 的实例。
5. 依赖注入:按照 BeanDefinition 中的信息,为 Bean 注入所需的属性或依赖(@Autowired)。
6. 执行 BeanPostProcessor:调用所有 BeanPostProcessor 对 Bean 实例进行增强。AOP 代理就是在此阶段生成的。
7. 容器就绪:完成所有单例 Bean 的初始化,容器启动完毕,应用程序可以正常使用这些 Bean。
简单记忆:初始化 -> 加载配置注册Bean定义 -> [BeanFactory后处理] -> 实例化 -> 依赖注入 -> [Bean后处理] -> 就绪。
(2)你了解的 Spring 都用到哪些设计模式?
Spring 框架中大量使用了设计模式,以下是一些最典型的:
1. 工厂模式 (Factory):BeanFactory 和 ApplicationContext 核心就是容器工厂,用于创建和管理 Bean 对象。
2. 单例模式 (Singleton):Spring Bean 的默认作用域就是单例,容器中只存在一个实例。
3. 代理模式 (Proxy):AOP 的实现基础。Spring 使用 JDK 动态代理和 CGLIB 代理来增强目标类。
4. 模板方法模式 (Template Method):JdbcTemplate、RestTemplate 等,封装了固定流程,将易变部分(如 SQL 语句)通过回调接口暴露给用户。
5. 观察者模式 (Observer):Spring 事件机制。ApplicationEvent 代表事件,ApplicationListener 是观察者,ApplicationContext 是事件发布者。
6. 适配器模式 (Adapter):Spring MVC 中的 HandlerAdapter,使得多种类型的 Controller(如 @Controller、Servlet)能够被统一调用。
7. 策略模式 (Strategy):在资源加载 (ResourceLoader)、AOP 代理创建等方面,根据不同情况使用不同的实现策略。
8. 装饰器模式 (Decorator):例如 HttpServletRequest 的包装类,用于增强原始对象的功能。
一句话总结:Spring 本身就是一个设计模式的最佳实践教科书。
(3)Spring 有哪几种事务传播行为?
Spring 事务定义了 7 种传播行为,用来规定当多个事务方法相互调用时,事务应该如何传播。
核心的 3 种:
1. REQUIRED(默认):
- 如果当前存在事务,则加入该事务。
- 如果当前没有事务,则创建一个新的事务。
2. REQUIRES_NEW:
- 总是创建一个新的事务,并暂停当前事务(如果存在)。
- 两个事务互不干扰,独立提交或回滚。
3. NESTED:
- 如果当前存在事务,则在嵌套事务中执行。
- 嵌套事务是外部事务的一部分,只有外部事务提交时,它才会提交。它有自己的回滚点,可以单独回滚而不影响外部事务。
其他 4 种:
1. SUPPORTS:如果当前存在事务,则加入;如果当前没有事务,就以非事务方式执行。
2. NOT_SUPPORTED:以非事务方式执行,并暂停当前事务(如果存在)。
3. MANDATORY:强制必须存在一个事务,否则抛出异常。
4. NEVER:强制必须不能存在任何事务,否则抛出异常。
一句话总结:
传播行为定义了事务的边界。最常用的是 REQUIRED(同生共死)和 REQUIRES_NEW(另起炉灶)。
(4)说说 Spring Boot 的启动流程?
Spring Boot 的启动流程可以简化为以下几个核心步骤:
1. 启动入口:执行 SpringApplication.run(...) 方法。
2. 初始化:
- 创建
SpringApplication实例。 - 进行初始化配置:推断应用类型(Servlet/Reactive)、加载
ApplicationContextInitializer和ApplicationListener(监听器)。
3. 运行应用:
- 创建并准备环境:加载配置文件(如
application.properties)、命令行参数等。 - 创建应用上下文:根据应用类型创建
ApplicationContext(例如,对于 Web 应用,创建AnnotationConfigServletWebServerApplicationContext)。 - 准备上下文:设置环境、执行
ApplicationContextInitializer、加载 Bean 定义(主要是通过@ComponentScan和自动配置)。 - 刷新上下文:这是最核心的一步,调用
AbstractApplicationContext.refresh()方法。这个过程完成了传统 Spring 容器的启动,包括 Bean 工厂设置、Bean 的实例化、依赖注入、执行BeanPostProcessor等。 - afterRefresh 回调:容器刷新完成后执行的一些扩展回调。
- 调用
CommandLineRunner和ApplicationRunner:执行应用中定义的这些 Runner 接口实现。
4. 启动完成:此时,内嵌的 Web 服务器(如 Tomcat)已经启动,应用开始监听指定端口,等待请求。
简单记忆:初始化配置 -> 准备环境 -> 创建上下文 -> 刷新上下文(核心)-> 执行Runner -> 启动完成。
(5)Spring Boot 是如何实现自动配置的?
Spring Boot 自动配置的核心原理是 @EnableAutoConfiguration 注解,其实现过程可以概括为以下几个关键步骤:
1. 启动注解:@SpringBootApplication 注解包含了 @EnableAutoConfiguration,它开启了自动配置。
2. 加载配置:@EnableAutoConfiguration 会通过 @Import 导入 AutoConfigurationImportSelector 类。
3. 选择配置类:这个类会从 META-INF/spring.factories 文件中加载所有预先定义好的全限定配置类名(例如 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration)。
4. 条件化装配:这些自动配置类上都有 @Conditional 系列注解(如 @ConditionalOnClass, @ConditionalOnProperty, @ConditionalOnMissingBean)。Spring 会根据当前项目的类路径、配置文件、已存在的 Bean 等条件来判断是否应该启用该配置。
5. 创建 Bean:如果一个自动配置类的所有条件都满足,它内部定义的 Bean(例如 DataSource, RedisTemplate)就会被创建并加入到 Spring 容器中。
举个例子:RedisAutoConfiguration 配置类上可能有 @ConditionalOnClass(RedisClient.class),意思是:只有当项目的类路径下存在 RedisClient 这个类时,这个自动配置才会生效。
一句话总结:Spring Boot 通过读取 spring.factories 文件中的预置配置类,并根据一系列条件注解(@Conditional)动态地决定是否将这些配置类中的Bean加载到容器中,从而实现“开箱即用”。
(6)如何理解 Spring Boot 中的 starter?
在 Spring Boot 中,Starter 是一组有用的依赖集合,用于简化构建配置。Spring Boot 通过 Starter POMs 提供了各种常用功能模块的集成,开发者只需引入一个 Starter 依赖,便可以自动获得该模块的所有相关依赖和配置,而无需手动添加多个依赖库和配置文件。
有点像我们去买一些必需品,然后有人打包成一个套餐售卖,我们就不需要一个一个再去找了,直接一次性打包拿走,节省了时间。
(7)Spring Boot 是如何通过 main 方法启动 web 项目的?
Spring Boot 通过 main 方法启动 Web 项目的核心在于 内嵌式 Servlet 容器。
启动流程可以简化为以下几步:
1. 执行 main 方法:调用 SpringApplication.run(Application.class, args)。
2. 启动 Spring 容器:
- 这个方法会初始化 Spring 容器(
ApplicationContext),完成所有 Bean 的创建和装配,包括自动配置。
3. 自动配置内嵌服务器:
- Spring Boot 的自动配置模块(如
TomcatServletWebServerFactoryAutoConfiguration)会检测到类路径下有 Servlet API 和相关服务器(如 Tomcat、Jetty),于是自动创建一个内嵌的 Servlet 容器实例(例如 Tomcat)。
4. 启动服务器并部署应用:
- 容器刷新过程中,会从容器中获取
ServletWebServerFactory并调用其getWebServer()方法。 - 这个方法会创建内嵌的 Tomcat 等服务器实例,并将我们的 Spring MVC 应用(
DispatcherServlet)部署到它上面。 - 最后,启动这个内嵌服务器,使其开始在指定端口(默认 8080)监听 HTTP 请求。
一句话总结:Spring Boot 的 main 方法通过启动 Spring 容器,触发自动配置来创建并启动一个内嵌的 Servlet 容器(如Tomcat),从而将整个应用作为一个独立的 Java 进程运行,无需部署到外部 Web 服务器。
(8)Spring Boot 的核心特性有哪些?
好的。Spring Boot 的核心特性可以概括为以下几点:
1. 自动配置 (Auto-configuration):根据项目的类路径、已有的 Bean 和配置,自动配置 Spring 应用,实现了“约定优于配置”,大部分情况无需手动编写配置。
2. 起步依赖 (Starter Dependencies): 提供了一系列预定义的依赖描述符(如 spring-boot-starter-web),只需引入一个起步依赖,就能获得开发某种功能(如 Web、JPA)所需的所有依赖库,解决了依赖冲突和版本管理问题。
3. 内嵌服务器 (Embedded Server):将 Tomcat、Jetty 或 Undertow 等 Servlet 容器内嵌到应用中,使得应用可以被打包成一个独立的 JAR 文件,并通过 java -jar 命令直接运行,无需部署到外部 Web 服务器。
4. 生产就绪 (Production-Ready):提供了一系列用于监控和管理生产环境的工具,如Actuator(可以查看应用健康状态、指标等信息)。
一句话总结:Spring Boot 通过自动配置、起步依赖和内嵌服务器三大核心特性,极大地简化了基于 Spring 的独立应用和微服务开发,实现了开箱即用和快速部署。
(9)什么是 Spring Boot?
Spring Boot 是一个基于 Spring 框架的快速开发脚手架。
它的核心设计目标是:简化 Spring 应用的初始搭建和开发过程。
它通过以下方式实现这一目标:
- 自动配置:无需编写繁琐的 XML 配置,大多数功能都可以“开箱即用”。
- 起步依赖:提供预定义的依赖组合,简化 Maven/Gradle 配置,解决版本冲突。
- 内嵌服务器:将 Tomcat 等服务器内嵌,允许你将应用打包成一个可独立运行的 JAR 文件。
一句话总结:Spring Boot 让开发者能快速创建出具备生产级别的、独立运行的 Spring 应用程序。
(10)什么是 Spring IOC?
Spring IoC(控制反转) 是 Spring 框架的核心。
1. 核心思想:控制权的转移
- 传统方式:程序由你在代码中“主动”创建和管理对象(
new Object())。 - IoC 方式:将对象的创建和依赖关系的控制权,从程序员手中“反转”交给了 Spring IoC 容器 来统一管理。
2. 实现方式:依赖注入 (DI)
IoC 是一种设计思想,而 依赖注入 (Dependency Injection) 是它的实现方式。容器通过依赖注入的方式来管理组件之间的依赖关系,而不是由组件自己创建或查找依赖。
3. 举个例子:
假设 A 类依赖于 B 类。
- 没有 IoC:你需要在
A的代码里new B()。 - 有 IoC:你只需要在
A中声明“我需要一个B”(比如通过@Autowired注解)。Spring 容器会自动创建好B的实例,并注入(装配)到A中。
总结一句话:
Spring IoC 就是一个容器,它负责创建、组装并管理你应用中所有的对象(称为 Bean),并通过依赖注入的方式将它们联系起来。
(11)Spring AOP 默认用的是什么动态代理,两者的区别?
Spring AOP 默认的动态代理策略是:
如果目标类实现了接口,则使用 JDK 动态代理;否则,使用 CGLIB 动态代理。
两者的核心区别:
| 特性 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 机制 | 基于接口 | 基于类继承 |
| 要求 | 目标类必须至少实现一个接口 | 目标类不能是 final(因为要继承) |
| 性能 | 生成较快,早期执行稍慢 | 生成较慢,但执行速度一般更快 |
| 依赖 | Java 标准库自带 | 需要引入 CGLIB 库 |
简单来说:JDK 代理要求有接口,它创建接口的代理实现;CGLIB 代理无此要求,它通过生成目标类的子类来代理。
(12)什么是 AOP?
AOP(面向切面编程) 是一种编程范式,用于将那些遍布在多个业务逻辑中的公共功能(称为横切关注点) 从业务代码中剥离出来,进行独立管理和集中处理。
核心概念:
- 横切关注点:像日志、事务、安全校验等,它们与核心业务逻辑无关,但又是多个模块都需要用到的功能。
- 核心思想:在不修改原有业务代码的前提下,通过“动态代理”技术,在程序运行的特定位置(如方法调用前、后)织入这些通用功能。
举个例子:
你有很多业务方法(下单、查询、付款),都需要记录日志。
- 没有 AOP:你需要在每个方法里都写
log.info(...)。 - 有 AOP:你只定义一个日志切面。Spring AOP 会自动在所有这些方法执行前后,帮你加上日志代码。
一句话总结:
AOP 让关注点分离,使我们能专注于核心业务,同时优雅地实现通用功能。
(13)看过源码吗?说下 Spring 由哪些重要的模块组成?
Spring 框架主要由以下重要模块组成,像一个分层架构:
1. 核心容器 (Core Container)
- Spring-Core:最基础模块,提供IoC(控制反转)功能。
- Spring-Beans:负责Bean的创建、配置和管理。
- Spring-Context:在Core和Beans之上,提供企业级服务(应用上下文、事件传播等)。
- Spring-SpEL:提供强大的表达式语言。
2. AOP与切面 (AOP and Aspects)
- Spring-AOP:提供面向切面编程的实现。
- Spring-Aspects:集成AspectJ框架。
3. 数据访问与集成 (Data Access/Integration)
- Spring-JDBC:提供了JDBC的抽象层,简化JDBC操作。
- Spring-TX:支持强大的声明式和编程式事务管理。
- Spring-ORM:集成主流ORM框架,如Hibernate、JPA。
4. Web层 (Web)
- Spring-Web:提供基本的Web集成功能。
- Spring-WebMVC:提供了MVC模式实现的Web框架。
5. 测试 (Test)
- Spring-Test:提供了对单元测试和集成测试的出色支持。
简单来说,就是从核心容器出发,通过AOP增强功能,并由数据访问和Web等模块提供具体领域支持,测试模块贯穿全程。
(14)什么是循环依赖(常问)?
循环依赖是指两个或多个模块、对象实例之间存在直接或间接的相互依赖关系,形成一个闭环 。在软件开发中,这是一种常见问题,它可能会导致代码难以理解、维护、测试和重用。具体类型如下:
- 直接循环依赖:两个模块直接相互依赖,比如模块A的代码中引入了模块B,而模块B的代码中又引入了模块A 。
- 间接循环依赖:三个或更多模块形成一个依赖闭环,例如模块A依赖模块B,模块B依赖模块C,而模块C又依赖模块A。
在Spring框架里,循环依赖主要指两个或两个以上的Bean互相依赖对方,最终形成闭环。比如Bean A依赖于Bean B,而Bean B又依赖于Bean A。
(15)Spring 如何解决循环依赖?(???)
关键就是提前暴露未完全创建完毕的 Bean。
在 Spring 中主要是使用三级缓存来解决了循环依赖:
- 一级缓存(Singleton Objects Map):用于存储完全初始化完成的单例Bean。
- 二级缓存(Early Singleton Objects Map):用于存储尚未完全初始化,但已实例化的Bean,用于提前暴露对象,避免循环依赖问题。
- 三级缓存(Singleton Factories Map):用于存储对象工厂,当需要时,可以通过工厂创建早期Bean(特别是为了支持AOP代理对象的创建)。
解决步骤:
- Spring 首先创建 Bean 实例,并将其加入三级缓存中(Factory)。
- 当一个 Bean 依赖另一个未初始化的 Bean 时,Spring 会从三级缓存中获取 Bean 的工厂,并生成该 Bean 的对象(若有代理则是代理对象)。
- 代理对象存入二级缓存,解决循环依赖。
- 一旦所有依赖 Bean 被完全初始化,Bean 将转移到一级缓存中。
(16)为什么 Spring 循环依赖需要三级缓存,二级不够吗?(???)
Spring 之所以需要三级缓存而不是简单的二级缓存,主要原因在于AOP代理和Bean的早期引用问题。
二级缓存虽然可以解决循环依赖的问题,但在涉及到动态代理(AOP) 时,直接使用二级缓存不做任何处理会导致我们拿到的 Bean 是未代理的原始对象。如果二级缓存内存放的都是代理对象,则违反了 Bean 的生命周期(AOP 代理就在初始化后阶段生成)。
(17)说下 Spring Bean 的生命周期?
Spring Bean 的生命周期可以概括为以下几个核心步骤:
1. 实例化:容器通过构造函数或工厂方法创建 Bean 的实例。
2. 属性赋值:容器注入 Bean 的依赖(通过 Setter 或字段注入)。
3. 初始化前:调用所有 BeanPostProcessor 的 postProcessBeforeInitialization 方法。
4. 初始化:如果 Bean 实现了 InitializingBean 接口,则调用 afterPropertiesSet 方法;如果配置了 init-method,则调用指定的方法。
5. 初始化后:调用所有 BeanPostProcessor 的 postProcessAfterInitialization 方法。AOP 代理就在此阶段生成。
6. 就绪:此时 Bean 已准备就绪,存在于应用上下文中,可以被使用。
7. 销毁:容器关闭时,如果 Bean 实现了 DisposableBean 接口,则调用 destroy 方法;如果配置了 destroy-method,则调用指定的方法。
简单记忆:实例化 -> 属性填充 -> [初始化前] -> 初始化 -> [初始化后] -> 使用 -> 销毁
(18)Spring MVC 具体的工作原理?
Spring MVC 的工作原理,核心是 DispatcherServlet 的处理流程,可以分为以下步骤:
1. 发送请求:用户发起请求,被 DispatcherServlet(前端控制器)统一接收。
2. 映射处理器:DispatcherServlet 询问 HandlerMapping:“该请求由哪个 Controller 处理?” HandlerMapping 返回对应的处理器(也叫 Handler)。
3. 调用处理器:DispatcherServlet 通过 HandlerAdapter 去真正调用上一步找到的处理器方法。
4. 执行控制器:Controller 执行业务逻辑,返回一个包含视图名和模型数据的 ModelAndView 对象。
5. 解析视图:DispatcherServlet 将视图名交给 ViewResolver(视图解析器)进行解析,得到具体的 View 对象(如 JSP、Thymeleaf模板)。
6. 渲染视图:View 对象结合 Model 中的数据,进行视图渲染,生成最终的响应内容(如 HTML)。
7. 返回响应:DispatcherServlet 将渲染结果返回给客户端。
简单记忆:
DispatcherServlet 收到请求 -> HandlerMapping 找谁处理 -> HandlerAdapter 去调用 -> Controller 处理返回结果 -> ViewResolver 找视图 -> View 渲染 -> 返回响应
(19)Spring 中的 DI 是什么?
DI(Dependency Injection,依赖注入)是IOC思想的具体实现方式:
容器在创建Bean时,自动将其依赖的对象(另一个 Bean、配置值等)注入到当前Bean中,替代手动new或查找依赖。
常见注入方式:构造器注入、Setter注入、字段注入
DI与IOC的关系:
- IOC是思想:强调“控制权转移”(容器管对象)。
- DI是手段:通过“注入依赖”的方式,实现IOC的思想落地。
简单说:IOC 是“要解决什么问题”,DI 是“具体怎么解决”。
Spring IOC 是“让容器接管对象控制权”的设计思想,通过 DI(依赖注入)的方式实现对象创建、依赖管理,最终带来解耦、易维护、可测试的好处,是 Spring 框架的核心基石。
更多推荐
所有评论(0)