Spring 面试要点深度解析
Spring 面试要点深度解析
在 Java 开发领域,Spring 框架几乎是绕不开的重要话题。无论是初学者还是经验丰富的开发者,在面试中都可能遇到关于 Spring 的各类问题。本文将深入剖析一些常见的 Spring 面试要点,带你全面理解 Spring 的核心概念与关键特性。
一、Spring 单例 bean 与线程安全
Spring 中的 bean 默认 scope 是 singleton(单例)。多个线程使用同一个单例 bean 时,若该 bean 内部存在共享可变状态,并且多个线程同时对其进行读写操作,就可能出现线程安全问题。例如,假设一个单例 bean 中有一个成员变量用于存储某种累积结果,多个线程同时调用该 bean 的修改此变量的方法,就可能导致数据混乱。
不过,若 bean 中的方法操作的是线程本地变量(ThreadLocal)或者该 bean 本身是无状态的(即没有共享可变的成员变量),那么即使在多线程环境下,单例 bean 也能保证线程安全。若担心线程安全问题,也可以将 bean 的 scope 设置为 prototype(多例),每次获取 bean 都是新的实例,从而避免多线程共享同一个实例带来的安全隐患,但要注意,prototype 范围的 bean 在某些场景下也会有潜在问题,比如在结合某些依赖注入场景时可能不符合预期,且 Spring 容器也不会对 prototype 范围的 bean 进行销毁相关的生命周期管理。
二、Spring AOP 剖析
Spring AOP(面向切面编程)是处理系统中横向切面(如日志记录、安全控制、事务管理等)的有力工具。它将这些通用的、与核心业务逻辑无关但又需要在多个模块中使用的功能封装成切面,然后在不修改原有业务代码的基础上,将其“切入”到指定的连接点(通常是方法执行前后等位置)。
例如,在一个电商系统中,我们可以利用 AOP 在用户登录相关的多个接口方法执行前后添加统一的日志记录切面,记录用户登录的时间、IP 地址等信息,这样既保证了核心业务代码的简洁性,又实现了通用功能的集中管理。
三、Spring 事务管理策略
Spring 提供了编程式事务管理和声明式事务管理两种方式。
编程式事务管理需要开发者在代码中手动控制事务的开始、提交和回滚,通常通过 TransactionTemplate 或者直接使用平台事务管理器(如 JpaTransactionManager 等)来实现。这种方式灵活性高,但代码侵入性强,使得业务代码与事务代码紧密耦合,维护成本较高,一般不作为首选方案。
声明式事务管理则更为推荐,它主要依赖于 AOP 实现,通过在配置文件或使用注解(如 @Transactional)的方式,就可以将事务规则应用到指定的方法上。比如,在一个银行转账服务的代码中,在转账相关的服务方法上添加 @Transactional 注解,一旦转账过程中出现异常,Spring 就会自动回滚事务,保证数据的一致性,这种方式使得代码更加简洁清晰,将事务管理与具体业务逻辑解耦。
四、事务失效的常见场景
- 抛出非 RuntimeException(非检查型异常) :Spring 默认只有遇到 RuntimeException 这种非检查型异常时才会触发事务回滚。如果抛出的是检查型异常(如 IOException 等),Spring 不会自动回滚事务。例如,在方法中故意抛出一个 Exception(检查型)来模拟错误情况,发现数据库中的事务并没有回滚。
- 抛出的异常被内部捕获处理 :即使方法内部抛出了 RuntimeException,但如果在方法内部使用 try - catch 将其捕获并处理了,那么 Spring 外部就无法感知到异常的发生,也就不会触发事务回滚。
- 方法内部调用本类中的其他带有 @Transactional 注解的方法 :在这种情况下,事务不会像预期那样起作用,因为 Spring 的事务是基于代理实现的,只有当外部调用带有 @Transactional 注解的方法时,Spring 才会创建代理对象并开启事务,而类内部的方法调用不会触发事务代理机制。
五、Bean 的生命周期之旅
- 加载 BeanDefinition :Spring 容器首先从配置元数据(如 XML 配置文件、注解等)中读取 bean 的定义信息,将其解析为 BeanDefinition 对象,并存储在容器中,这个对象包含了 bean 的基本信息,如类名、属性值、初始化方法等。
- 实例化 :当需要创建 bean 实例时,Spring 容器根据 BeanDefinition 中的信息,通过反射等方式调用 bean 的构造方法进行实例化。
- 属性填充(依赖注入) :在实例化完成后,Spring 容器会根据配置,将 bean 所需要的依赖(如其他 bean 或基本数据类型等)注入到该 bean 的对应属性中,完成依赖关系的构建。
- 处理 aware 接口 :如果 bean 实现了某些 aware 接口(如 BeanNameAware、BeanFactoryAware 等),Spring 容器会将相关的资源(如 bean 的名称、BeanFactory 引用等)传递给该 bean,以便 bean 能够获取到某些 Spring 容器级别的信息。
- 初始化前置处理(BeanPostProcessor) :在这一步,Spring 容器会调用所有注册的 BeanPostProcessor 的 postProcessBeforeInitialization 方法,这些处理器可以在 bean 初始化之前对 bean 进行一些额外的处理,比如添加一些自定义的包装逻辑等。
- 初始化 :如果 bean 配置了初始化方法(如通过 init-method 属性指定的方法,或者实现了 InitializingBean 接口中的 afterPropertiesSet 方法),Spring 容器会调用该方法,完成 bean 的初始化操作,这一步通常用于执行一些资源的初始化等工作。
- 初始化后置处理(BeanPostProcessor) :紧接着,Spring 容器会调用所有注册的 BeanPostProcessor 的 postProcessAfterInitialization 方法,可以在这里进行一些进一步的检查或增强操作。
- 销毁 :当 Spring 容器关闭时,对于配置了销毁方法(如通过 destroy-method 属性指定的方法,或者实现了 DisposableBean 接口中的 destroy 方法)的 bean,Spring 容器会调用其销毁方法,用于释放资源等收尾工作。
六、Spring 循环引用的探究
在 Spring 中,循环引用是指多个 bean 之间相互依赖,形成一个循环的引用链。例如,类 A 的构造方法中依赖于类 B 的实例,而类 B 的构造方法中又依赖于类 A 的实例,这就形成了一种构造方法注入的循环依赖。
Spring 通过三级缓存机制来解决循环引用问题。
- 一级缓存 :主要是在 bean 实例化完成后,将单例 bean 存入一级缓存(一个 ConcurrentHashMap 类型的 map),这样后续对同一个 bean 的获取就可以直接从一级缓存中取出。
- 二级缓存 :在 bean 实例化完成但尚未进行属性填充之前,将 bean 放入二级缓存。当有其他 bean 正在获取这个还在创建中的 bean 时,可以从二级缓存中获取一个提前暴露的对象(早期暴露的 bean 实例),从而打破循环依赖的僵局。
- 三级缓存 :用于存储 ObjectFactory 类型的对象。当一个 bean 正在创建过程中,其他 bean 需要引用这个尚未完全创建好的 bean 时,可以通过三级缓存中的 ObjectFactory 获取到这个正在创建中的 bean 的一个代理对象,使得循环依赖能够在一定程度上得到解决。
不过需要注意的是,三级缓存并不能解决所有的循环引用问题。例如,当涉及到构造方法注入的循环依赖且构造方法参数顺序导致无法满足依赖时,或者对于 prototype 范围的 bean(因为 prototype 范围的 bean 不会被缓存)等情况,Spring 依然无法解决循环引用问题。
七、SpringMvc 执行流程梳理
- 请求到达前端控制器(DispatcherServlet) :用户发起的 HTTP 请求首先被 SpringMvc 的前端控制器 DispatcherServlet 接收,它作为整个请求处理的入口。
- 处理器映射(HandlerMapping) :DispatcherServlet 会根据请求的 URL 等信息,通过 HandlerMapping 查找对应的处理器(Controller 中的方法)。HandlerMapping 会返回一个 HandlerExecutionChain 对象,其中包含了处理该请求的处理器以及相关的拦截器。
- 处理器适配(HandlerAdapter) :DispatcherServlet 将请求和找到的处理器交给 HandlerAdapter 来处理。HandlerAdapter 负责执行对应的处理器方法,并返回一个 ModelAndView 对象,该对象包含了模型数据和视图名称等信息。
- 视图解析(ViewResolver) :HandlerAdapter 将处理后的 ModelAndView 对象返回给 DispatcherServlet,DispatcherServlet 再将视图名称交给 ViewResolver 进行解析,ViewResolver 会根据视图名称找到对应的视图对象(如 JSP 页面、HTML 模板等)。
- 视图渲染与响应 :DispatcherServlet 将模型数据填充到视图中,完成页面的渲染,然后将最终的响应结果返回给用户,呈现给用户一个完整的页面内容。
掌握好 SpringMvc 的执行流程,有助于开发者更好地理解 Web 请求在 SpringMvc 框架中的流转过程,从而更精准地定位问题、进行功能扩展和优化。