Spring 6 源码深度掘金:66+核心原理与高频面试攻坚指南
一、Core & IoC容器(19题)
Spring 6 中的 AOT(Ahead-of-Time)编译原理
答:
- 编译时机: 在 应用构建阶段(而非运行时)进行分析和预编译。
- 输入分析:
- 扫描应用代码(
@Configuration
类、@Bean
方法等)。 - 推断Spring应用上下文的结构(Bean定义、依赖关系、配置条件)。
- 识别运行时必需的动态特性(如代理、反射、资源加载)。
- 扫描应用代码(
- 预生成:
- 根据分析结果,预先生成 必要的、高效的、可预测的 Java代码(或字节码)。
- 替代传统Spring在启动时进行的运行时字节码生成(如CGLIB/AOP代理)、类路径扫描、配置解析和Bean定义推导。
- 输出: 生成优化的 “应用上下文初始化器”代码,直接构建预解析好的Bean定义,绕过运行时的反射和动态处理。
- 目标:
- 显著减少启动时间: 消除了启动时的动态处理开销。
- 降低内存占用: 减少运行时代码生成和元数据占用。
- 原生镜像支持基石: 为使用GraalVM生成轻量级、快速启动的原生可执行文件提供必需的静态分析信息和预生成代码。
总结: Spring AOT 在构建期静态分析应用,预生成高效的初始化代码,替代耗时的运行时动态处理,从而优化启动性能和资源消耗,并为原生编译铺路。
Spring框架的核心功能有哪些?
答:IoC(控制反转)和AOP(面向切面编程)。
解释IoC和DI,它们之间的关系是什么?
答:IoC是设计思想(控制权反转),DI是实现方式(依赖注入)。Spring通过DI实现IoC。
BeanFactory和ApplicationContext的区别?
答:ApplicationContext是BeanFactory的子接口,提供更多企业级功能(如事件发布、国际化等),且默认预加载单例Bean。
特性 | BeanFactory | ApplicationContext |
---|---|---|
Bean加载方式 | 懒加载 | 启动时预加载单例Bean |
国际化支持 | ❌ | ✅ (MessageSource) |
事件发布机制 | ❌ | ✅ (ApplicationEventPublisher) |
AOP集成 | 需手动配置 | 内置支持 |
Spring Bean的作用域?
- Singleton(全局单例,无状态服务)
- 范围:整个IoC容器。
- 生命周期:容器启动时创建(如果设置为懒加载,则在第一次使用时创建),容器关闭时销毁。
- 适用场景:无状态的Bean,比如配置类、工具类、服务类等。
- Prototype(原型)
- 范围:每次获取时创建新实例。
- 生命周期:创建后由调用者负责管理,容器不负责销毁。因此,要注意资源释放问题。
- 适用场景:有状态的Bean,比如每次请求需要独立状态的Bean。
- Request(请求)
- 范围:一次HTTP请求。
- 生命周期:请求开始创建,请求结束销毁。
- 适用场景:存储请求相关的数据,比如表单数据、请求参数等。
- Session(会话)
- 范围:一个用户会话(HTTP Session)。
- 生命周期:会话创建时创建,会话超时或销毁时销毁。
- 适用场景:用户会话相关的数据,比如用户登录信息、购物车等。
- Application(应用)
- 范围:整个Web应用(ServletContext)。
- 生命周期:Web应用启动时创建,应用停止时销毁。
- 与Singleton区别:Singleton是Spring容器级别的单例,而Application是ServletContext级别的单例。在同一个ServletContext中,即使是多个Spring容器(如父子容器)也可能会有多个Application作用域的Bean实例?实际上,通常一个Web应用只有一个Spring容器,所以两者区别不大。但在某些特定场景,比如多个DispatcherServlet,每个DispatcherServlet有自己的容器,但共享同一个ServletContext,此时Application作用域的Bean在ServletContext中是唯一的。
- WebSocket
- 范围:一个WebSocket会话。
- 生命周期:WebSocket会话开始时创建,结束时销毁。
- 适用场景:WebSocket通信过程中保存状态。
如何配置Bean的作用域?
答:XML中scope
属性,或注解@Scope("prototype")
。
Bean的生命周期详细步骤?
答:实例化→属性填充→BeanNameAware→BeanFactoryAware→ApplicationContextAware→BeanPostProcessor前置处理→@PostConstruct→InitializingBean→自定义init方法→BeanPostProcessor后置处理→使用中→@PreDestroy→DisposableBean→自定义destroy方法。
什么是循环依赖?Spring如何解决?
- 难点:构造器注入无法解决循环依赖
- 答:多个Bean相互引用形成环。Spring通过三级缓存解决(singletonFactories、earlySingletonObjects、singletonObjects),仅支持单例Setter循环依赖:
// 三级缓存结构
singletonFactories // 三级:存放Bean工厂(ObjectFactory)
earlySingletonObjects // 二级:存放早期引用
singletonObjects // 一级:存放完整Bean
三级缓存解决循环依赖流程(Spring 6 优化)
关键点:三级缓存(singletonFactories)存储
ObjectFactory`,支持 AOP 代理对象的提前创建
构造器循环依赖能否解决?为什么?
答:不能。因为构造器注入必须在实例化阶段完成,此时Bean未放入缓存。
@Autowired和@Resource的区别?
@Autowired
按类型注入,支持@Qualifier
指定名称。@Resource
(JDK标准注解)默认按名称注入,名称找不到则按类型。
如何注入集合类型(List/Map)?
答:XML中通过<list>
、<map>
标签,或使用@Autowired
注入所有匹配类型的Bean。
@Component, @Service, @Controller, @Repository的区别?
答:功能相同,均为声明Bean。语义区分:
@Controller
:Web层
@Service
:业务层
@Repository
:数据层(转换持久层异常)
@Component
:通用组件
什么是延迟初始化(Lazy Initialization)?
答:Bean在首次使用时才创建,而非容器启动时。通过@Lazy(true)
配置。
如何条件化创建Bean?
答:使用@Conditional
注解,实现Condition
接口自定义条件。
public class MyCondition implements Condition {public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta) {return ctx.getEnvironment().containsProperty("enable.feature");}
}
FactoryBean和BeanFactory的区别?
答:
BeanFactory
是IoC容器根接口。FactoryBean
是创建复杂对象的工厂Bean,通过getObject()
返回实际对象。
如何获取ApplicationContext?
答:实现ApplicationContextAware
接口或直接注入ApplicationContext
。
BeanDefinition的作用?
答:定义Bean的元数据(类名、作用域、属性等),Spring根据BeanDefinition创建Bean。
PropertySourcesPlaceholderConfigurer的作用?
答:解析${}
占位符,替换为属性文件中的值。
Spring如何管理父子容器?
答:通过HierarchicalBeanFactory
接口,子容器可以访问父容器的Bean,但父容器不能访问子容器。
二、AOP(12题)
AOP的核心概念(Aspect, Joinpoint, Pointcut, Advice)?
答:
- 切面(Aspect):模块化的横切关注点(如日志)。
- 连接点(Joinpoint):程序执行点(方法调用、异常抛出)。
- 切点(Pointcut):匹配连接点的表达式。
- 通知(Advice):在连接点执行的动作(Before/After等)。
Spring AOP和AspectJ的区别?
特性 | Spring AOP | AspectJ |
---|---|---|
概念 | Spring AOP是Spring框架中的AOP实现,依赖于Spring的容器和其他基础设施 | AspectJ是一个独立的AOP框架,不依赖于任何框架或容器 |
实现 | 动态代理(JDK/CGLIB) | 编译时/加载时织入 |
性能 | 运行时开销 | 无运行时开销 |
功能 | 仅支持方法级别 | 支持字段、构造器等 |
JDK动态代理和CGLIB代理的区别?
答: Spring6仍优先使用 JDK 动态代理(需接口),但 CGLIB 移除了对 ASM 的依赖,改用 ByteBuddy
- JDK代理:基于接口,使用
Proxy
和InvocationHandler
。 - CGLIB:基于继承,生成目标类的子类。
如何强制使用CGLIB代理?
答:XML配置<aop:aspectj-autoproxy proxy-target-class="true"/>
,或注解@EnableAspectJAutoProxy(proxyTargetClass=true)
。
五种通知类型及其执行时机?
答:
@Before
:方法执行前@After
:方法执行后(无论成功或异常)@AfterReturning
:方法正常返回后@AfterThrowing
:方法抛出异常后@Around
:环绕方法执行(可控制是否执行目标方法)
如何定义切点(Pointcut)表达式?
答:使用AspectJ表达式,如execution(* com.service.*.*(..))
。
如何在同一切面中控制通知的执行顺序?
答:实现Ordered
接口或使用@Order
注解,值越小优先级越高。
如何获取目标方法的参数?
答:在通知方法中使用JoinPoint
参数(非环绕)或ProceedingJoinPoint
(环绕),调用getArgs()
。
如何修改目标方法的返回值?
答:在@AfterReturning
中通过returning
属性绑定返回值并修改,或在@Around
中修改proceed()
的返回值。
如何捕获目标方法抛出的异常?
答:在@AfterThrowing
中通过throwing
属性绑定异常,或在@Around
中捕获proceed()
的异常。
AOP失效的常见场景
答:
- 目标类未由Spring管理
- 方法非public
- 自调用(this.method())
- 未通过代理对象调用方法
@AspectJ与Schema-based AOP对比
特性 | @AspectJ风格 | XML Schema风格 |
---|---|---|
可读性 | ✅ (注解直观) | ❌ (配置冗长) |
编译依赖 | ❌ (纯运行时) | ❌ |
切点表达式 | ✅ (强大灵活) | ⚠️ (功能受限) |
三、事务管理(9题)
Spring事务管理的两种方式?
答:
- 编程式:
TransactionTemplate.execute()
- 声明式:
@Transactional
注解
声明式事务实现原理
答:基于AOP + TransactionInterceptor
拦截器
@Transactional注解可以作用在哪些位置?
答:类(所有public方法)、接口(不推荐)、方法(推荐public方法)。
事务传播行为(Propagation)有哪些?
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodA() {// 新建独立事务,外层事务挂起
}
答:
REQUIRED
(默认):当前有事务则加入,否则新建。REQUIRES_NEW
:新建事务,挂起当前事务。SUPPORTS
:有事务则加入,否则非事务运行。NOT_SUPPORTED
:非事务运行,挂起当前事务。MANDATORY
:必须存在事务,否则抛异常。NEVER
:必须不存在事务,否则抛异常。NESTED
:嵌套事务(使用保存点)。
事务隔离级别(Isolation)有哪些?
答:
DEFAULT
:使用数据库默认。READ_UNCOMMITTED
:读未提交。READ_COMMITTED
:读已提交。REPEATABLE_READ
:可重复读。SERIALIZABLE
:串行化。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ_UNCOMMITTED | ✅ | ✅ | ✅ |
READ_COMMITTED | ❌ | ✅ | ✅ |
REPEATABLE_READ | ❌ | ❌ | ✅ |
SERIALIZABLE | ❌ | ❌ | ❌ |
@Transactional失效的常见场景?
答:
- 方法非public。
- 自调用(未经过代理)。
- 异常被捕获未抛出。
- 异常类型非
RuntimeException
且未指定rollbackFor
。 - 数据库引擎不支持事务(如MyISAM)。
// 自调用导致事务失效
public void save() {this.update(); // 未经过代理类
}
@Transactional
public void update() {...}
调用方式 | 是否经过代理 | 事务是否生效 |
---|---|---|
外部调用update() | ✅ | ✅ |
this.update() | ❌ | ❌ |
- 解决:注入自身代理对象
@Autowired private MyService self;
如何指定回滚的异常类型?
答:使用@Transactional(rollbackFor = MyException.class)
。
事务超时如何设置?
答:@Transactional(timeout = 5)
(单位:秒)。
只读事务的作用?
答:@Transactional(readOnly=true)
,优化数据库引擎(如MySQL只读时使用InnoDB只读视图)。
四、Spring MVC(12题)
Spring MVC核心组件及其作用?
答:
DispatcherServlet
:前端控制器,统一调度。HandlerMapping
:映射请求到处理器。HandlerAdapter
:执行处理器。ViewResolver
:解析视图名到具体视图。HandlerExceptionResolver
:处理异常。
DispatcherServlet请求处理流程?
@RequestMapping和@GetMapping的区别?
答:
@GetMapping
是@RequestMapping(method=RequestMethod.GET)
的简写。- @RequestMapping底层:
RequestMappingHandlerMapping
解析注解注册路由
如何获取请求参数?参数绑定原理
答: 关键类:HandlerMethodArgumentResolver
实现类
@RequestParam
:获取查询参数。@PathVariable
:获取路径参数。@RequestBody
:获取请求体(JSON/XML)。HttpServletRequest
:原生对象。
如何返回JSON数据?
答:使用@RestController
或@ResponseBody
,并添加Jackson依赖。
如何实现文件上传?
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {file.transferTo(new File("/path/to/save"));
}
如何统一处理异常?
答:使用@ControllerAdvice
+ @ExceptionHandler
。
@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)public ModelAndView handleException(Exception ex) {ModelAndView mav = new ModelAndView("error");mav.addObject("msg", ex.getMessage());return mav;}
}
拦截器(Interceptor) vs 过滤器(Filter)?
答:
- 过滤器:Servlet规范,处理所有请求。
- 拦截器:Spring MVC机制,可访问Handler上下文。
如何自定义拦截器?
答:实现HandlerInterceptor
接口,重写preHandle
、postHandle
、afterCompletion
方法,并注册到WebMvcConfigurer
。
如何实现重定向?
答:返回"redirect:/path"
。
如何获取请求头信息?
答:使用@RequestHeader("User-Agent") String userAgent
。
如何实现数据验证?
答:使用@Valid
注解参数,并配合JSR-303注解(如@NotNull
)在实体类上。
五、Spring Data JPA(8题)
JPA和Hibernate的关系?
答:JPA是规范,Hibernate是实现。
Spring Data JPA的核心接口?
答:CrudRepository
(基础CRUD)、JpaRepository
(扩展分页/排序)。
如何定义查询方法?
答:根据方法名自动解析,如findByUsername(String username)
。
@Query注解的作用?
答:自定义JPQL或原生SQL查询。
如何实现分页查询?
Page<User> findAll(Pageable pageable);
// 调用:repository.findAll(PageRequest.of(0, 10));
实体类状态有哪些?
答:瞬时(Transient)、托管(Managed)、游离(Detached)、删除(Removed)。
如何解决N+1查询问题?
答:
- 使用
@EntityGraph
指定关联加载策略。 - JPQL中写
JOIN FETCH
。
@EntityGraph(attributePaths = "orders") // 指定立即加载的关联
List<User> findAll();
乐观锁实现
@Entity
public class Product {@Versionprivate Long version; // 更新时自动校验版本
}
六、高频难点(10题)
FactoryBean与普通Bean的区别
答:FactoryBean返回的是getObject()
方法创建的对象,而非自身;FactoryBean
优先初始化,但其生产的 Bean 延迟加载(需通过 &
前缀获取 FactoryBean 本身)
BeanPostProcessor扩展点实战
public class CustomBeanPostProcessor implements BeanPostProcessor {public Object postProcessBeforeInitialization(Object bean, String name) {if (bean instanceof MyService) {// 修改Bean实例}return bean;}
}
如何动态注册Bean?
答:通过BeanDefinitionRegistry
编程式注册:
GenericBeanDefinition beanDef = new GenericBeanDefinition();
beanDef.setBeanClass(MyBean.class);
registry.registerBeanDefinition("myBean", beanDef);
Spring事件机制模型
- 三要素:
ApplicationEvent
(事件)→ApplicationListener
(监听器)→ApplicationEventPublisher
(发布器)
@Transactional事务传播嵌套场景分析
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {methodB(); // 内嵌事务
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {...}
AOT 编译对 Bean 生命周期的限制?
答:AOT 模式下不支持动态代理,需提前生成代理类;BeanPostProcessor
需静态注册。
响应式编程在 Spring 6 的整合
答:ReactiveAdapterRegistry
支持 Project Reactor/RxJava,与 Spring WebFlux 深度集成。
如何实现 GraalVM 原生镜像支持?
答:
- 使用
spring-boot-aot-plugin
生成反射配置 - 排除动态代理类
- 通过
@NativeHint
定义原生元数据。
Spring 6 中的 JdbcClient
新特性
答:简化 JDBC 操作,链式调用代替 JdbcTemplate
jdbcClient.sql("SELECT * FROM user WHERE id=?")
.param(1)
.query(User.class);
ProblemDetail 与国际化(i18n)的整合
答:通过 MessageSource
动态填充错误消息
detail.setDetail(messageSource.getMessage("error.not_found", null, locale));