文章目录
【Spring全家桶必问篇】2025年精选高频面试题深度解析(附代码示例)
金三银四,金九银十,又到了面试季。Spring作为Java开发领域事实上的标准,无疑是面试中绕不开的重中之重。本文系统梳理了Spring Framework、Spring MVC、Spring Boot及Spring Cloud中的核心高频面试题,并附上详细解析和代码示例,助你一举拿下心仪的Offer!
1. Spring Framework 核心概念
1.1 什么是IoC(控制反转)和DI(依赖注入)?它们有什么关系?
-
IoC (Inversion of Control): 控制反转,是一种设计思想。它将传统上由程序代码直接操控的对象调用权交给容器(如Spring IoC容器)来统一管理。简单说,就是将对象的创建、依赖关系的组装控制权从应用程序代码中反转到外部容器。
-
DI (Dependency Injection): 依赖注入,是实现IoC思想的一种主要方式。容器通过反射机制,动态地将某个依赖对象注入到组件(如Bean)中。常见注入方式有:构造器注入、Setter方法注入、字段注入。
-
关系: DI是IoC的实现手段之一。我们通过DI这种具体技术来实现IoC的松散耦合设计目标。
代码示例(构造器注入):
// Service 层
public interface UserService {
String getUserName();
}
@Service
public class UserServiceImpl implements UserService {
@Override
public String getUserName() {
return "程序员";
}
}
// Controller 层,通过构造器注入 UserService
@Controller
public class UserController {
private final UserService userService;
// Spring容器会自动查找UserService类型的Bean并注入此处
public UserController(UserService userService) {
this.userService = userService;
}
public void printUserName() {
System.out.println(userService.getUserName());
}
}
1.2 Spring中Bean的作用域(Scope)有哪些?
Spring为Bean定义了多种作用域,你可以通过@Scope
注解来指定。
作用域 | 描述 | 适用场景 |
---|---|---|
singleton (默认) | IoC容器中仅存在一个Bean实例,所有对该Bean的引用都共享这一实例。 | 无状态的Bean,如工具类、Service、DAO等。 |
prototype | 每次从容器中获取Bean时,都会创建一个新的实例。 | 有状态的Bean,需要保持各自状态的Bean。 |
request | 每一次HTTP请求都会创建一个新的Bean,仅在Web应用中有效。 | 用于存储一次请求的上下文信息。 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅在Web应用中有效。 | 用于存储用户会话信息,如用户登录状态。 |
application | 一个ServletContext生命周期内只创建一个Bean。 | 全局上下文信息。 |
websocket | 在一个WebSocket的生命周期内只创建一个Bean。 | WebSocket通信相关。 |
配置示例:
@Bean
@Scope("prototype") // 或者 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyPrototypeBean myPrototypeBean() {
return new MyPrototypeBean();
}
1.3 Spring Bean的生命周期是怎样的?
这是一个非常经典的问题。简单来说,一个Bean从创建到销毁经历了以下关键步骤:
- 实例化 (Instantiate): 容器通过反射调用构造方法创建Bean实例。
- 属性赋值 (Populate Properties): 容器注入Bean的依赖(其他Bean)和配置值(如
@Value
)。 - BeanPostProcessor前置处理: 调用所有
BeanPostProcessor
的postProcessBeforeInitialization
方法。 - 初始化 (Initialization):
- 如果Bean实现了
InitializingBean
接口,调用afterPropertiesSet()
方法。 - 调用通过
@Bean(initMethod = "...")
或xml init-method
指定的自定义初始化方法。
- 如果Bean实现了
- BeanPostProcessor后置处理: 调用所有
BeanPostProcessor
的postProcessAfterInitialization
方法。AOP代理就是在此阶段生成的。 - 使用 (Ready to Use): Bean已就绪,存在于应用上下文中,可以被其他对象使用。
- 销毁 (Destruction):
- 容器关闭时,如果Bean实现了
DisposableBean
接口,调用destroy()
方法。 - 调用通过
@Bean(destroyMethod = "...")
或xml destroy-method
指定的自定义销毁方法。
- 容器关闭时,如果Bean实现了
1.4 什么是AOP?Spring AOP有哪些实现方式?
-
AOP (Aspect-Oriented Programming): 面向切面编程。它将那些与核心业务逻辑无关的横切关注点(如日志、事务、安全等)模块化,并通过“横切”的方式织入到业务逻辑中,从而减少系统的重复代码,降低模块间的耦合度。
-
核心概念:
- Aspect (切面): 横切关注点的模块化,通常是一个类(使用
@Aspect
注解)。 - Advice (通知): 切面在特定连接点执行的动作(如
@Before
,@After
,@Around
等)。 - Pointcut (切点): 匹配连接点的表达式,定义了通知何时被执行。
- Join Point (连接点): 程序执行过程中的一个点,如方法调用、异常抛出等(Spring AOP中特指方法执行)。
- Aspect (切面): 横切关注点的模块化,通常是一个类(使用
-
实现方式:
- 基于代理(默认):
- JDK动态代理: 针对实现了接口的目标类。Spring默认使用此方式。
- CGLIB动态代理: 针对未实现接口的目标类。通过生成目标类的子类来实现代理。
- 基于代理(默认):
代码示例(记录方法执行时间):
@Aspect
@Component
public class PerformanceAspect {
// 定义切点:匹配com.example.demo.service包下所有类的所有方法
@Pointcut("execution(* com.example.demo.service.*.*(..))")
public void serviceLayer() {}
// 环绕通知
@Around("serviceLayer()")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = pjp.proceed(); // 执行目标方法
long endTime = System.currentTimeMillis();
System.out.println(pjp.getSignature() + " 方法执行耗时: " + (endTime - startTime) + "ms");
return result;
}
}
2. Spring MVC
2.1 请描述Spring MVC的工作流程(请求处理流程)
这是一个必考的核心题,最好能结合流程图来理解。
核心流程如下:
- DispatcherServlet: 作为前端控制器,接收所有请求。
- HandlerMapping:
DispatcherServlet
查询一个或多个HandlerMapping
,找到处理该请求的处理器(Handler / Controller)。 - HandlerAdapter:
DispatcherServlet
将请求交给合适的HandlerAdapter
来执行具体的Controller。 - Controller:
HandlerAdapter
调用真正的Controller方法处理请求,并返回一个ModelAndView
对象(包含模型数据和视图名)。 - ViewResolver:
DispatcherServlet
将返回的逻辑视图名解析为具体的View对象。 - View:
View
对象负责渲染视图(结合Model中的数据),最终生成响应内容(如HTML、JSON)。 - Response:
DispatcherServlet
将渲染结果返回给客户端。
2.2 @RestController
和@Controller
有什么区别?
特性 | @Controller | @RestController |
---|---|---|
本质 | 是@Component 的特殊化,标明一个类是MVC中的Controller。 | @Controller + @ResponseBody 的组合注解。 |
返回值 | 通常返回一个视图名(String),由ViewResolver解析为物理视图。 | 方法返回值直接通过HttpMessageConverter写入响应体,通常返回JSON/XML数据。 |
用途 | 传统的前后端不分离的Web应用,返回HTML页面。 | 前后端分离的Web应用或RESTful Web服务,提供API接口。 |
3. Spring Boot
3.1 Spring Boot的核心优势是什么?它解决了哪些痛点?
Spring Boot不是用来替代Spring的,而是为了更快、更简单地使用Spring。
- 优势与解决的痛点:
- 简化配置: 约定大于配置,提供了大量自动化配置(Auto Configuration),极大减少了繁琐的XML或Java配置。
- 独立运行: 内嵌了Tomcat、Jetty等Servlet容器,可以打包成可执行的JAR,直接通过
java -jar
命令运行。 - 简化依赖管理: 通过Starter POMs提供了一系列项目依赖的“一站式”解决方案,避免了依赖冲突。
- 生产就绪: 提供了诸如健康检查、 metrics、外部化配置等开箱即用的生产特性(Actuator模块)。
3.2 Spring Boot的自动配置是如何实现的?
自动配置是Spring Boot的魔法之源,其核心是@EnableAutoConfiguration
注解。
- 启动注解:
@SpringBootApplication
注解包含了@EnableAutoConfiguration
。 - 加载配置:
@EnableAutoConfiguration
会利用AutoConfigurationImportSelector
这个类。 - 寻找配置: 这个类会从
META-INF/spring.factories
文件中读取所有预先配置好的自动配置类(XXXAutoConfiguration
)的全限定名。 - 条件装配: 这些自动配置类上都有大量的
@ConditionalOnXxx
注解(如@ConditionalOnClass
,@ConditionalOnProperty
)。Spring Boot会根据当前项目的类路径、已有的Bean、配置文件等条件来决定是否启用该配置。 - 创建Bean: 如果条件满足,配置类中定义的Bean就会被创建并加入到IoC容器中。
例如:当你引入了spring-boot-starter-web
依赖(包含了Spring MVC和Tomcat的jar包),ServletWebServerFactoryAutoConfiguration
和DispatcherServletAutoConfiguration
等配置类的条件就会满足,从而自动为你配置好内嵌Tomcat和Spring MVC。
4. Spring Cloud
4.1 微服务架构下,如何保证服务间的调用是可靠的?
在微服务网络中,服务故障是常态。Spring Cloud提供了一系列组件来提升系统的弹性(Resilience)和容错能力。
- 服务熔断(Circuit Breaker): 由 Spring Cloud CircuitBreaker (抽象) 或 Sentinel 实现。当某个目标服务调用慢或失败比例过高时,熔断器会打开,后续调用直接快速失败(fallback),不再请求目标服务。防止雪崩效应。
- 服务降级(Fallback): 熔断器打开后或服务调用失败时,提供一种备选方案(fallback方法),返回一个托底数据或友好提示,保证主业务的可用性。
- 负载均衡(Load Balance): 由 Ribbon 或 LoadBalancer 实现。在服务消费者端实现负载均衡,将请求分摊到多个服务实例上。
- 服务限流(Rate Limiting): 由 Sentinel 实现。控制服务的QPS或并发线程数,防止被突发流量冲垮。
代码示例(使用@SentinelResource
实现熔断降级):
@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;
// 定义资源点,并指定降级方法
@SentinelResource(value = "getUserById", blockHandler = "getUserByIdBlockHandler")
public UserDto getUserById(Long id) {
// 模拟远程调用用户服务
return restTemplate.getForObject("http://user-service/users/" + id, UserDto.class);
}
// 降级方法(参数和返回值需与原方法一致,最后加一个BlockException参数)
public UserDto getUserByIdBlockHandler(Long id, BlockException ex) {
// 返回托底数据
return new UserDto(-1L, "默认用户", "服务暂时不可用,请稍后再试");
}
}