为什么 Spring 是面试必考点?

在Java后端开发领域,Spring 框架凭借其轻量级、解耦和强大的生态系统,成为了企业级应用开发的事实标准。无论是初创公司还是大型互联网企业,Spring 及其衍生项目(如Spring Boot, Spring Cloud)都是构建微服务架构和单体应用的首选。因此,在技术面试中,面试官往往会针对 Spring 的核心原理进行深入考察,以评估候选人的基础扎实程度。

对于准备 Spring面试题目及答案 的求职者来说,仅仅记住答案是不够的。您需要理解 Spring 设计背后的哲学,例如“约定优于配置”、“面向接口编程”以及“控制反转”等理念。本站提供的 Spring面试题 不仅包含标准答案,还结合了实际开发场景,帮助您从原理层面掌握知识。

⚡ IOC 控制反转

理解 Spring IOC 容器如何管理Bean的生命周期。面试常问:Bean的默认作用域是什么?singleton和prototype的区别?如何避免单例模式下的线程安全问题?

⚙️ AOP 面向切面

掌握 Spring AOP 的实现机制,包括JDK动态代理和CGLIB代理的选择。常见考点:AOP的通知类型有哪些?@Transactional事务失效的场景有哪些?

🚀 Spring Boot 自动配置

深入剖析 Spring Boot 的自动配置原理,了解 @EnableAutoConfiguration 和 spring.factories 文件的作用。这是当前Java开发岗位的必备技能。

Spring 核心原理深度解析

为了更清晰地展示 Spring面试题 中的高频考点,我们将核心原理分为三个主要部分。请点击下方选项卡查看详细内容。

Bean 的生命周期

Bean的生命周期 是 Spring面试 中几乎必考的题目。一个Bean从创建到销毁,经历了以下几个关键阶段:

  1. 实例化 (Instantiation):Spring容器通过构造函数或工厂方法创建Bean实例。
  2. 属性赋值 (Populate Bean):为Bean的属性设置值,并解析依赖注入。
  3. 初始化 (Initialization):
    • 如果Bean实现了 BeanNameAware 接口,调用 setBeanName()。
    • 如果Bean实现了 BeanFactoryAware 接口,调用 setBeanFactory()。
    • 执行 BeanPostProcessor.postProcessBeforeInitialization()。
    • 如果Bean指定了 init-method 或实现了 InitializingBean,调用初始化方法。
    • 执行 BeanPostProcessor.postProcessAfterInitialization()。这是AOP代理生成的关键时刻。
  4. 使用 (Usage):Bean处于可用状态,供应用程序调用。
  5. 销毁 (Destruction):
    • 如果Bean实现了 DisposableBean 接口,调用 destroy()。
    • 如果Bean指定了 destroy-method,调用销毁方法。

注意:在面试中,面试官可能会追问 BeanPostProcessor 的作用。它是Spring提供的扩展点,允许在Bean初始化前后进行自定义处理,是许多Spring功能(如AOP、@Autowired)的基础。

IOC 容器的实现机制

Spring IOC 容器是Spring框架的核心,它负责管理对象的生命周期和依赖关系。实现IOC主要依赖于反射和工厂模式。

1. BeanDefinition

在Spring中,每个Bean的信息都被封装在 BeanDefinition 对象中。它包含了Bean的类名、作用域、构造函数参数、属性值等信息。容器启动时,会解析配置文件或注解,生成对应的 BeanDefinition 并存入 BeanDefinitionRegistry。

2. 依赖注入 (DI)

依赖注入是IOC的具体实现方式。Spring支持三种注入方式:

  • 构造器注入:推荐用于必填依赖,保证Bean不可变性。
  • Setter注入:适用于可选依赖,灵活性高。
  • 字段注入 (@Autowired):使用最广泛,但降低了代码的可测试性。

3. 循环依赖解决

Spring通过三级缓存解决单例Bean的循环依赖问题:

  1. singletonObjects:存放完全初始化的Bean。
  2. earlySingletonObjects:存放原始的Bean对象(未填充属性),用于解决循环依赖。
  3. singletonFactories:存放Bean工厂,用于生成早期引用。

注意:Spring只能解决单例作用域下的setter注入循环依赖,构造器注入和原型作用域下的循环依赖无法解决。

AOP 代理机制

Spring AOP 通过动态代理实现切面编程。Spring根据目标类是否实现了接口,选择不同的代理策略:

1. JDK Dynamic Proxy

如果目标类实现了至少一个接口,Spring默认使用JDK动态代理。它基于接口创建代理类,在运行时生成实现接口的匿名内部类。

// 伪代码示例
Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new InvocationHandler() { ... }
);

2. CGLIB

如果目标类没有实现接口,Spring会使用CGLIB库生成目标类的子类作为代理。CGLIB通过字节码技术生成子类,覆盖目标方法并织入切面逻辑。

3. 代理选择

可以通过 @EnableAspectJAutoProxy(exposeProxy = true) 或 proxyTargetClass = true 强制使用CGLIB。在现代Spring Boot应用中,通常建议让业务类实现接口,以便使用JDK代理,减少依赖外部库。

Spring Boot 面试核心

随着微服务架构的流行,Spring Boot 已成为Java开发的主流框架。面试中关于 Spring Boot 的问题通常聚焦于其自动配置原理、Starter机制以及常见配置。

1. 自动配置原理

Spring Boot 的核心是 @SpringBootApplication,它组合了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan。其中,@EnableAutoConfiguration 是关键。

当启用自动配置时,Spring Boot 会扫描 spring-boot-autoconfigure jar包中的 META-INF/spring.factories 文件(Spring Boot 2.7+ 为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)。该文件列出了所有可能的自动配置类。Spring Boot 根据当前类路径下的依赖和Bean,通过 @Conditional 注解(如 @ConditionalOnClass、@ConditionalOnMissingBean)决定是否加载这些配置类。

2. Starter 机制

Spring-Boot-Starter 是一组便捷的依赖描述符。例如,spring-boot-starter-web 会自动引入Spring MVC、Tomcat、Jackson等依赖。开发者只需引入一个Starter,即可快速搭建特定功能的项目环境,避免了手动管理版本冲突的麻烦。

3. 内嵌容器

Spring Boot 默认内嵌Tomcat、Jetty或Undertow。启动时,它会自动配置这些容器的Bean,使得应用可以独立运行,无需部署到外部Web服务器。这极大地简化了部署流程。

4. 常见面试题

Q: 如何自定义Starter?

A: 创建一个普通Maven项目,编写自动配置类(使用@Configuration和@Conditional注解),然后在META-INF/spring.factories中注册该配置类。最后,将依赖打包并发布。

Q: Spring Boot 如何优化启动速度?

A: 1. 使用 spring.main.lazy-initialization=true 开启懒加载;2. 减少自动配置的扫描范围;3. 使用GraalVM进行原生镜像编译。

Spring Cloud 微服务架构

对于高级Java开发岗位,Spring Cloud 是必须掌握的技能。它提供了一整套微服务解决方案,包括服务注册与发现、配置中心、网关、负载均衡等。

组件名称 功能描述 常用实现
服务注册与发现 管理微服务实例的健康状态和地址信息 Eureka, Nacos, Consul
配置中心 集中管理微服务的配置文件 Spring Cloud Config, Nacos
负载均衡 在多个服务实例间分发请求 Ribbon, Spring Cloud LoadBalancer
服务网关 统一入口,处理鉴权、限流、路由 Zuul, Spring Cloud Gateway
熔断器 防止雪崩效应,提高系统容错性 Hystrix, Resilience4j
分布式追踪 追踪请求在微服务间的调用链路 Sleuth + Zipkin, SkyWalking

网友还关心:Nacos vs Eureka

在当前技术选型中,Nacos 逐渐取代 Eureka 成为主流的服务注册中心。Nacos 同时支持 AP 和 CP 模式,而 Eureka 仅支持 AP。此外,Nacos 还集成了配置中心功能,简化了架构复杂度。对于新项目,强烈建议使用 Nacos。

网友还关心:高频问答

以下整理了求职者最常搜索的 Spring面试题目及答案,请仔细阅读并理解其背后的原理。

Q1: @Autowired 和 @Resource 的区别是什么?

@Autowired 是Spring提供的注解,默认按类型(byType)注入,如果有多个同类型Bean,则按名称(byName)匹配。它只能用于字段、方法或构造器。@Resource 是JDK提供的注解(JSR-250),默认按名称(byName)注入,如果找不到则按类型匹配。它支持 name 和 type 属性指定。建议优先使用 @Autowired,除非需要指定具体的Bean名称。

Q2: Spring 中的事务传播行为有哪些?

Spring定义了7种事务传播行为:
1. REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。
2. REQUIRES_NEW:创建一个新事务,如果当前存在事务,则把当前事务挂起。
3. SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
4. NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则把当前事务挂起。
5. NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
6. NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与REQUIRED类似的操作。
7. MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

Q3: 如何解决 Spring 中的循环依赖?

Spring主要通过三级缓存解决单例Bean的setter注入循环依赖。当A依赖B,B依赖A时:
1. Spring创建A,将A的ObjectFactory放入三级缓存。
2. A填充属性时,发现依赖B,创建B。
3. B填充属性时,发现依赖A,从三级缓存获取A的ObjectFactory,生成A的早期引用(未填充属性的实例),放入二级缓存,并从三级缓存移除。
4. B完成初始化,放入一级缓存。
5. A获取B的引用,完成初始化,放入一级缓存。
注意:构造器注入的循环依赖无法解决,会抛出BeanCurrentlyInCreationException。

Q4: Spring MVC 的工作流程是怎样的?

1. 用户发送请求至前端控制器 DispatcherServlet。
2. DispatcherServlet 调用 HandlerMapping 查找处理器。
3. HandlerMapping 找到具体的处理器,返回 HandlerExecutionChain。
4. DispatcherServlet 调用 HandlerAdapter 适配处理器。
5. HandlerAdapter 执行具体的 Controller(Handler)。
6. Controller 执行业务逻辑,返回 ModelAndView。
7. HandlerAdapter 将 ModelAndView 返回给 DispatcherServlet。
8. DispatcherServlet 调用 ViewResolver 解析视图。
9. ViewResolver 返回具体的 View。
10. DispatcherServlet 对 View 进行渲染(填充数据),返回响应给用户。

Q5: Spring Boot 中如何自定义 Starter?

1. 创建一个新的Maven项目。
2. 编写自动配置类,使用 @Configuration 和 @ConditionalOnClass 等注解。
3. 在 src/main/resources/META-INF/ 下创建 spring.factories 文件(Spring Boot 2.7+ 为 spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)。
4. 在文件中注册你的自动配置类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\n com.example.MyAutoConfiguration
5. 打包并发布到Maven仓库,其他项目引入该依赖即可。