spring
【问题】 谈谈你对Spring的理解?Spring能帮我们做什么?
【参考答案】 Spring是一个开源的轻量级Java企业级应用开发框架,其核心是控制反转(IoC,Inversion of Control)和面向切面编程(AOP,Aspect-Oriented Programming)。Spring通过IoC容器管理对象的生命周期和依赖关系,通过AOP实现横切关注点(如日志、事务)的解耦,从而简化了企业级应用的开发。Spring框架不仅仅是一个IoC容器,它提供了一整套完整的解决方案,涵盖了数据访问、事务管理、Web层开发、消息传递、测试等多个方面,使开发者能够专注于业务逻辑,而无需关心底层基础设施的复杂性。
具体来说,Spring能帮我们做以下事情:
-
IoC容器管理对象(Bean)
Spring IoC容器负责创建、配置、组装对象(称为Bean),并管理它们的完整生命周期。开发者只需通过配置(XML、注解、Java Config)定义Bean之间的依赖关系,容器会自动完成依赖注入,降低了组件之间的耦合度。 -
面向切面编程(AOP)
Spring AOP允许开发者将横切关注点(如日志记录、性能监控、安全控制、事务管理)从业务代码中分离出来,通过声明式的方式动态织入,提高代码的模块化和可维护性。 -
声明式事务管理
Spring提供了统一的事务抽象层,支持编程式事务和声明式事务(基于AOP)。开发者只需通过注解或XML配置事务属性(如传播行为、隔离级别),即可实现复杂的事务管理,无需手动处理连接、提交、回滚等操作。 -
数据访问集成
Spring对主流的数据访问技术(JDBC、Hibernate、JPA、MyBatis等)提供了无缝集成,简化了数据访问代码的编写。例如,Spring的JdbcTemplate消除了传统的JDBC样板代码,而ORM集成则通过一致的异常体系简化了异常处理。 -
Web层开发(Spring MVC)
Spring MVC是一个基于Servlet API的轻量级Web框架,它实现了MVC设计模式,提供了强大的请求映射、数据绑定、验证、视图解析等功能。开发者可以快速构建灵活的Web应用程序,并与其他视图技术(JSP、Thymeleaf、FreeMarker)无缝集成。 -
与其他框架和技术的集成
Spring能够轻松地与各种企业级技术整合,如JavaMail(邮件发送)、任务调度(Quartz、@Scheduled)、缓存(Ehcache、Redis)、消息中间件(JMS、RabbitMQ)等。Spring的“集成”能力使得它成为企业级应用开发的事实标准。 -
测试支持
Spring提供了一套测试框架,支持单元测试和集成测试。通过@TestContext框架,可以在测试中轻松加载Spring容器,进行依赖注入,并对事务进行回滚控制,方便数据层测试。 -
Spring生态的扩展
基于Spring Framework,后续发展出了Spring Boot(快速配置)、Spring Cloud(微服务)、Spring Security(安全)、Spring Data(数据访问)等一系列子项目,形成了一个庞大的技术生态,能够应对从简单应用到复杂微服务架构的各种需求。
总之,Spring通过其核心的IoC和AOP,提供了一致、透明、高效的编程模型,极大地简化了Java企业级应用的开发、测试和部署。
【大白话解释于举例说明】 可以把Spring想象成一个万能管家,你只需要告诉它你需要什么,它就会帮你准备好,并协调好它们之间的关系。
-
IoC(控制反转):以前你自己去市场买菜、洗菜、炒菜(自己创建对象、管理依赖),现在你告诉管家你想吃鱼香肉丝(声明依赖),管家会自动把菜做好端到你面前(容器创建对象并注入依赖)。你只需要关注怎么吃(业务逻辑),不用管菜怎么来的。
-
AOP(面向切面编程):比如你每次吃饭前都要洗手(日志记录),吃饭后要擦嘴(事务提交)。如果每顿饭都自己去做这些事,太麻烦。管家可以在你吃饭前自动帮你洗手,吃饭后自动帮你擦嘴,你只管吃饭。这就是AOP,把通用操作从业务中剥离出来。
-
事务管理:就像管家帮你记账:你每次花钱(数据库操作),管家会自动记录(开启事务),如果钱不够了(异常),他会取消这笔交易(回滚),如果一切顺利,他会帮你确认支付(提交)。你只需要说“买一瓶水”,其他交给管家。
-
数据访问集成:你想查订单数据,以前要自己连接数据库、写JDBC、处理异常。现在你告诉管家“我要查订单”,管家会帮你用最顺手的方式(比如Hibernate)去查,你只管拿结果。
-
Web MVC:就像管家帮你处理前台接待:有客人(HTTP请求)来了,管家会根据门牌号(URL)找到对应的人(Controller),然后把客人的话(请求参数)转达给他,再把他的话翻译成客人能听懂的语言(视图)返回。
总之,Spring就是这样一个管家,把繁杂的底层工作都包了,让你专注于业务本身。
【扩展知识点详解】
-
Spring框架的模块化组成
Spring Framework由约20个模块组成,分为核心容器、数据访问/集成、Web、AOP/仪器、消息、测试等。核心容器包括spring-core、spring-beans、spring-context、spring-expression等,其中spring-context支持国际化、事件传播等。数据访问层包括JDBC、ORM、OXM、JMS、事务管理模块。 - IoC容器的两种实现
- BeanFactory:基础容器,提供基本的DI功能,采用延迟初始化(第一次调用
getBean()时才创建Bean)。 - ApplicationContext:高级容器,继承自BeanFactory,添加了国际化、事件传播、AOP集成、声明式机制等特性,通常使用它作为容器。常见实现有
ClassPathXmlApplicationContext、AnnotationConfigApplicationContext等。
- BeanFactory:基础容器,提供基本的DI功能,采用延迟初始化(第一次调用
- 依赖注入的方式
- 构造器注入:强制依赖,保证不可变性。
- Setter注入:可选依赖,允许重新配置。
- 字段注入(通过注解如
@Autowired):简洁但不利于测试和不可变性。
-
AOP的实现原理
Spring AOP基于动态代理实现:如果目标类实现了接口,则使用JDK动态代理;否则使用CGLIB生成子类代理。通知(Advice)有五种类型:前置(Before)、后置(AfterReturning)、异常(AfterThrowing)、最终(After)、环绕(Around)。切面(Aspect)通过Pointcut表达式定义连接点。 -
事务管理的核心接口
Spring事务抽象的核心接口是PlatformTransactionManager,针对不同持久化技术有不同的实现,如DataSourceTransactionManager(JDBC/MyBatis)、HibernateTransactionManager、JpaTransactionManager。声明式事务通过@Transactional注解配置,可定义传播行为(Propagation)、隔离级别(Isolation)、超时、只读等属性。 -
传播行为详解
常见的传播行为:REQUIRED(支持当前事务,无则新建)、REQUIRES_NEW(挂起当前,新建独立事务)、SUPPORTS(支持当前,无则以非事务方式执行)、MANDATORY(必须存在当前事务,否则抛异常)、NOT_SUPPORTED、NEVER、NESTED(嵌套事务)等。 - Spring MVC的核心组件
DispatcherServlet:前端控制器,接收请求并分发。HandlerMapping:根据请求URL找到对应的Controller。HandlerAdapter:调用Controller的方法。ViewResolver:根据逻辑视图名解析实际视图。ModelAndView:封装模型数据和视图信息。
-
Spring Boot与Spring的关系
Spring Boot是基于Spring Framework的快速开发脚手架,它通过自动配置、起步依赖、嵌入式服务器等特性,极大地简化了Spring应用的初始搭建和开发过程。Spring Boot本身并不替代Spring,而是让Spring更容易使用。 - Spring的常用注解
- 声明Bean:
@Component、@Service、@Repository、@Controller。 - 注入依赖:
@Autowired、@Resource、@Inject。 - 配置类:
@Configuration、@Bean、@ComponentScan。 - 事务:
@Transactional。 - MVC:
@RequestMapping、@GetMapping、@PostMapping、@RequestBody、@ResponseBody等。
- 声明Bean:
- Spring的设计思想
- 非侵入式:应用代码无需继承Spring特定类,减少对框架的依赖。
- 开闭原则:通过IoC和AOP实现扩展开放、修改封闭。
- 约定优于配置:尤其在Spring Boot中体现。
- 分层架构:各模块职责清晰,可单独使用。
- Spring与Java EE的关系
Spring早期是对Java EE(当时叫J2EE)的补充,提供了更轻量级的替代方案。随着Java EE演进(现在是Jakarta EE),Spring依然保持活力,并且在微服务时代通过Spring Cloud成为主流选择。
【问题】 BeanFactory和ApplicationContext有什么区别?
【参考答案】 BeanFactory和ApplicationContext都是Spring框架中用于管理Bean的容器接口,其中ApplicationContext是BeanFactory的子接口,提供了更多企业级功能。它们的核心区别如下:
- 功能范围
- BeanFactory:是Spring最底层的IoC容器接口,提供了基础的依赖注入和Bean生命周期管理功能,包括Bean的加载、实例化、依赖维护等。
- ApplicationContext:继承自BeanFactory,除BeanFactory的所有功能外,还扩展了:
- 国际化支持(通过MessageSource)
- 资源访问(如URL和文件资源)
- 事件发布与监听机制(ApplicationEvent)
- 多配置文件加载
- 与Spring AOP、Web应用等更紧密的集成
- Bean的加载时机
- BeanFactory:采用延迟加载(Lazy-loading),只有在调用
getBean()获取Bean时,才进行实例化和依赖注入。 - ApplicationContext:采用预加载(Pre-loading),在容器启动时就会实例化所有单例Bean(默认)。这种方式可以尽早发现配置错误,但会增加启动时间和内存占用。
- BeanFactory:采用延迟加载(Lazy-loading),只有在调用
- 创建方式
- BeanFactory:通常通过编程方式创建,如
new XmlBeanFactory(new ClassPathResource("beans.xml"))(现已废弃,但原理类似)。 - ApplicationContext:支持声明式创建,如在web.xml中配置
ContextLoaderListener,或通过new ClassPathXmlApplicationContext("applicationContext.xml")等。
- BeanFactory:通常通过编程方式创建,如
- 后置处理器的注册
- BeanFactory:需要手动注册
BeanPostProcessor和BeanFactoryPostProcessor。 - ApplicationContext:会自动检测并注册这些后置处理器,无需手动配置。
- BeanFactory:需要手动注册
- 事件发布
- BeanFactory:不支持事件机制。
- ApplicationContext:内置事件发布机制,允许Bean通过实现
ApplicationListener接口监听容器事件(如上下文刷新、关闭等)。
- 应用场景
- BeanFactory:适用于资源有限、对内存敏感的环境,如移动设备或嵌入式系统。但在标准企业应用中较少直接使用。
- ApplicationContext:适用于大多数企业级应用,因为它提供了更丰富的功能,且与Spring生态无缝集成。
【大白话解释于举例说明】 可以把Spring容器想象成一个智能仓库,里面存放着各种零件(Bean)。
-
BeanFactory 是一个基础仓库,只提供最基本的存取功能。你什么时候需要零件,它什么时候给你拿出来(延迟加载)。仓库里没有广播系统(事件),没有多语言说明书(国际化),也没有统一的物资搬运车(资源访问)。它很省电(内存占用少),但功能单一。
-
ApplicationContext 是一个高级仓库,不仅存零件,还配备了各种便利设施:它有公告栏(事件机制),可以贴通知让大家知道仓库几点开门;有翻译员(国际化),能看懂多国语言的操作手册;有物流系统(资源访问),可以统一调配物资;还有质检员(后置处理器),自动检查零件质量。虽然每天开门时要把所有零件都摆出来(预加载),有点费电(启动慢),但用起来特别顺手。
举例:如果你写一个简单的Demo,只用到IoC,用BeanFactory就够了。但如果你需要事务管理、AOP、Web支持,那必须用ApplicationContext,因为它在启动时就帮你把复杂配置都处理好了。
【扩展知识点详解】
- BeanFactory的常见实现类
XmlBeanFactory:基于XML配置的经典实现(已废弃,建议使用DefaultListableBeanFactory配合XmlBeanDefinitionReader)。DefaultListableBeanFactory:当前最常用的BeanFactory实现,支持多种Bean定义读取方式。
- ApplicationContext的常见实现类
ClassPathXmlApplicationContext:从类路径加载XML配置文件。FileSystemXmlApplicationContext:从文件系统加载XML配置文件。AnnotationConfigApplicationContext:基于注解和Java配置类的容器,常用于Spring Boot。WebApplicationContext:专为Web应用设计,通过ContextLoaderListener初始化。
- Bean的生命周期管理
- 两者都支持Bean的生命周期回调,如
InitializingBean、DisposableBean接口,以及自定义的init-method和destroy-method。 - ApplicationContext在预加载时会立即调用Bean的初始化方法,而BeanFactory则等到获取时才会调用。
- 两者都支持Bean的生命周期回调,如
- 延迟加载与预加载的选择
- 预加载的优势:提前发现配置错误,提高首次访问速度;适合单例Bean较多的场景。
- 延迟加载的优势:节省启动时间,降低内存占用;适合原型Bean(多例)或资源受限环境。
- 可以通过
@Lazy注解或在XML中配置lazy-init="true"让ApplicationContext中的某些Bean延迟加载。
- BeanFactory和ApplicationContext与BeanPostProcessor的交互
BeanPostProcessor允许在Bean初始化前后进行自定义处理(如代理包装)。- ApplicationContext会自动扫描并注册所有实现了
BeanPostProcessor的Bean;而BeanFactory需要手动调用addBeanPostProcessor方法添加。
- 事件机制详解
- ApplicationContext通过
ApplicationEvent和ApplicationListener实现观察者模式。 - 内置事件:
ContextRefreshedEvent(刷新)、ContextStartedEvent(启动)、ContextStoppedEvent(停止)、ContextClosedEvent(关闭)。 - 开发者也可自定义事件并发布。
- ApplicationContext通过
- 国际化(i18n)支持
- 通过
MessageSource接口实现,ApplicationContext继承了该接口,可以直接使用getMessage方法获取国际化消息,底层依赖于ResourceBundle。
- 通过
- 资源访问
- ApplicationContext实现了
ResourceLoader接口,可以通过getResource方法获取Resource对象,支持类路径、文件系统、URL等多种资源类型。
- ApplicationContext实现了
- Web应用中的ApplicationContext
- Web应用通常有两个上下文:根上下文(由
ContextLoaderListener加载,包含所有Web层之外的Bean)和Web层上下文(由DispatcherServlet加载,包含控制器等)。两者构成父子关系,子上下文可以访问父上下文中的Bean。
- Web应用通常有两个上下文:根上下文(由
- 为什么开发中几乎不使用BeanFactory
- 因为Spring生态(如Spring MVC、Spring Boot)大量依赖于ApplicationContext提供的功能(如事件、资源加载、AOP自动代理等)。BeanFactory过于底层,无法满足企业级开发的需求,除非在极度受限环境中。
IOC与AOP
【问题】 请谈谈你对Spring核心(IoC和AOP)的理解,包括IoC的初始化过程、AOP的核心概念和通知类型。
【参考答案】 Spring框架的核心是控制反转(IoC)和面向切面编程(AOP)。
一、Spring IoC(控制反转)
-
IoC与DI的概念
IoC(Inversion of Control)是一种设计思想,指将传统上由程序代码直接控制的对象创建权交给容器来实现。Spring通过IoC容器管理对象的生命周期和依赖关系。DI(Dependency Injection)是IoC的一种实现方式,即容器在创建对象时动态地将依赖注入到对象中(通常通过构造方法、setter方法或字段)。 -
IoC容器的核心作用
Spring IoC容器负责读取配置元数据(XML、注解或Java Config),将其解析为BeanDefinition,然后根据BeanDefinition实例化、配置和组装Bean。容器本质上是一个工厂,内部维护了一个Bean定义注册表和单例缓存池(可理解为Map结构)。 -
IoC容器的初始化过程
以ApplicationContext为例,初始化主要分为三个步骤:- 定位:通过ResourceLoader加载配置资源(如XML文件、注解类)。
- 解析与注册:将配置信息解析为BeanDefinition,并注册到BeanDefinitionRegistry中。
- 实例化与依赖注入:容器启动时(或首次请求时)通过反射实例化Bean,并执行依赖注入,同时调用BeanPostProcessor等扩展点完成初始化。
二、Spring AOP(面向切面编程)
-
AOP的概念
AOP通过预编译方式和运行期动态代理,将那些与业务无关但被多个模块共享的行为(如日志、事务、安全)封装成可重用的模块(称为切面),从而减少代码重复,降低耦合。 - AOP的核心概念
- 切面(Aspect):横切关注点的模块化,如日志切面。
- 连接点(Joinpoint):程序执行过程中可插入切面的点(如方法调用)。
- 切入点(Pointcut):定义一组连接点,通知将在这些点上执行。
- 通知(Advice):切面在特定连接点执行的动作,Spring支持五种通知类型。
- 目标对象(Target):被代理的原始对象。
- 织入(Weaving):将切面应用到目标对象并创建代理对象的过程。
- 引入(Introduction):在不修改代码的前提下,为类动态添加新方法或属性。
- 通知类型
- 前置通知(@Before):在目标方法执行前执行。
- 后置通知(@After):在目标方法执行后执行(无论是否异常)。
- 返回通知(@AfterReturning):在目标方法正常返回后执行。
- 异常通知(@AfterThrowing):在目标方法抛出异常后执行。
- 环绕通知(@Around):包围目标方法,可自定义方法执行前后行为。
- AOP的实现机制
Spring AOP基于动态代理实现:若目标类实现了接口,则使用JDK动态代理;否则使用CGLIB生成子类代理。AspectJ则提供静态代理(编译时织入),性能更好但需要特定编译器。
【大白话解释于举例说明】
- IoC:好比你想吃饭,传统方式是自己买菜、做饭、洗碗(new对象、维护依赖)。有了IoC容器(餐厅),你只需点菜(声明依赖),服务员(容器)就会把做好的菜端给你,你只管吃(业务逻辑)。
- DI:餐厅做菜时,需要根据你的口味(配置)自动放入盐、辣椒(依赖对象),这就是依赖注入。
- AOP:餐厅为了记录每个顾客点了什么菜(日志),会在每个菜品制作前后自动拍照记录,而不需要在每个菜谱里写拍照代码。这个拍照功能就是一个切面。
- 通知类型举例:
- 前置通知:上菜前检查餐具是否干净。
- 后置通知:上菜后收拾桌子。
- 返回通知:顾客吃完满意离席时送上一句祝福。
- 异常通知:顾客投诉时及时处理。
- 环绕通知:服务员全程跟踪,从点菜到结账全流程掌控。
【扩展知识点详解】
-
Bean的生命周期
Spring IoC中Bean的完整生命周期包括:实例化→属性赋值→初始化前(BeanPostProcessor前置处理)→初始化(InitializingBean或init-method)→初始化后(BeanPostProcessor后置处理)→使用中→销毁前(DisposableBean或destroy-method)→销毁。 -
作用域
Spring Bean支持多种作用域:singleton(默认)、prototype、request、session、application、websocket。不同作用域影响Bean的创建和缓存策略。 -
循环依赖
Spring通过三级缓存解决单例Bean的循环依赖问题。一级缓存(成品池)、二级缓存(早期暴露池)、三级缓存(工厂池)。构造器循环依赖无法解决。 - AOP的织入时机
- 编译时织入(AspectJ):需要特殊编译器。
- 类加载时织入(AspectJ LTW):通过类加载器实现。
- 运行时织入(Spring AOP):通过动态代理,在运行时创建代理对象。
-
Spring AOP与AspectJ的关系
Spring AOP集成了AspectJ的注解风格(如@Aspect),但底层仍使用自己的动态代理实现。若需要更强大的AOP功能(如对字段、构造器进行切面),可单独使用AspectJ。 - JDK动态代理与CGLIB的对比
- JDK动态代理:要求目标类实现接口,通过反射调用方法,性能稍低。
- CGLIB:通过生成目标类的子类,覆盖方法实现增强,不能代理final类和方法。性能较高(但生成代理类较慢)。
Spring Boot 2.x默认使用CGLIB(通过spring.aop.proxy-target-class=true)。
- Spring IoC容器的高级特性
- 国际化(MessageSource)
- 事件发布(ApplicationEventPublisher)
- 资源加载(ResourceLoader)
- 类型转换(ConversionService)
- 属性解析(Environment)
-
BeanFactory与ApplicationContext的补充
ApplicationContext除了具备BeanFactory的所有功能外,还自动注册BeanPostProcessor、支持事件发布、国际化等,是更强大的容器。 - 常用注解
- 定义Bean:@Component、@Service、@Repository、@Controller、@Bean
- 注入:@Autowired、@Resource、@Inject
- 配置:@Configuration、@ComponentScan、@PropertySource
- AOP:@Aspect、@Pointcut、@Before等
- 设计模式
Spring大量使用设计模式,如:工厂模式(BeanFactory)、单例模式(Bean默认作用域)、代理模式(AOP)、模板方法(JdbcTemplate)、观察者模式(事件机制)等。
设计模式
【问题】 Spring中的设计模式有哪些?
【参考答案】 Spring框架在其设计和实现中广泛运用了经典的设计模式,这些模式使得Spring具有高度的可扩展性、灵活性和可维护性。以下是Spring中常见的设计模式及其具体体现:
-
单例模式(Singleton)
Spring容器管理的Bean默认作用域为单例(singleton),即每个Bean名称在容器中只存在一个实例。单例模式减少了对象创建和垃圾回收的开销,并保证共享资源的唯一性。 -
工厂模式(Factory)
Spring的IoC容器本质上是一个大工厂,通过BeanFactory和ApplicationContext接口生产和管理Bean。工厂模式将对象的创建与使用分离,降低了客户端与具体实现类的耦合。 -
代理模式(Proxy)
Spring AOP基于动态代理实现,为目标对象生成代理对象,并在代理对象中织入增强逻辑。JDK动态代理(针对接口)和CGLIB代理(针对类)是两种具体实现。 -
模板方法模式(Template Method)
Spring中大量模板类如JdbcTemplate、RestTemplate、JmsTemplate等,它们定义了算法的骨架(如获取连接、处理结果、关闭资源),将可变步骤延迟到回调接口或子类实现,从而减少重复代码。 -
策略模式(Strategy)
Spring的资源访问接口Resource具有不同实现(如ClassPathResource、FileSystemResource),对应不同资源获取策略。此外,Bean的实例化策略、事务管理策略等也体现了策略模式。 -
观察者模式(Observer)
Spring的事件机制基于ApplicationEvent和ApplicationListener实现了观察者模式。当容器发布事件时,所有注册的监听器自动触发相应逻辑,实现组件间的解耦。 -
适配器模式(Adapter)
Spring MVC中的HandlerAdapter用于适配不同类型的处理器(如@Controller方法、HttpRequestHandler),统一调用方式。AOP中的AdvisorAdapter将通知(Advice)适配为拦截器链。 -
责任链模式(Chain of Responsibility)
Spring MVC的拦截器链(HandlerInterceptor)和AOP的通知链(MethodInterceptor)均采用责任链模式,将请求依次传递给多个处理器,每个处理器可决定是否继续执行。 -
前端控制器模式(Front Controller)
Spring MVC的DispatcherServlet作为前端控制器,集中处理所有请求,进行分发、视图解析、异常处理等,简化了Web层的请求处理流程。 -
原型模式(Prototype)
Spring允许将Bean的作用域配置为prototype,每次请求(getBean)都会创建一个新实例,体现了原型模式。适用于有状态的Bean,避免线程安全问题。 -
装饰器模式(Decorator)
Spring中BeanDefinitionDecorator用于扩展Bean定义,例如<context:component-scan>内部通过装饰器添加注解处理器。此外,HttpServletResponseWrapper等包装类也体现了装饰器思想。 -
建造者模式(Builder)
Spring的BeanDefinitionBuilder以流式方式构建BeanDefinition,简化了编程式配置。EmbeddedDatabaseBuilder等也使用了建造者模式。 -
桥接模式(Bridge)
Spring的视图解析体系将视图(View)和视图解析器(ViewResolver)分离,两者可以独立变化,通过桥接模式组合使用。 -
组合模式(Composite)
Spring Cache中的CompositeCacheManager组合多个缓存管理器,统一管理;CompositeClassLoader等也体现了组合模式。
此外,依赖注入(DI)本身虽然不是GoF设计模式,但它是Spring实现控制反转的核心机制,可视为一种更宏观的模式。
【大白话解释于举例说明】
- 单例模式:就像公司只有一个CEO,所有人都找他都是同一个人。Spring的Bean默认是单例,节省内存,避免重复创建。
- 工厂模式:像汽车工厂,你只需要告诉它你想要什么车(配置),它帮你造好(
getBean)。你不需要知道造车细节。 - 代理模式:明星的经纪人,你找明星拍戏要先找经纪人,经纪人帮你谈合同、收钱,明星只负责演戏。Spring AOP就是给方法加个经纪人(代理),在方法前后做日志、事务。
- 模板方法模式:做饭的菜谱,步骤固定(备菜、炒菜、装盘),但具体做什么菜由你做。
JdbcTemplate固定了数据库操作步骤,你只需提供SQL和回调。 - 策略模式:去上班可以选择公交、地铁、打车,不同方式就是不同策略。Spring的
Resource接口让你用统一方式访问不同来源的资源(文件、类路径、URL)。 - 观察者模式:公众号订阅,你关注了公众号,当有新文章发布,你会收到通知。Spring事件机制中,监听者订阅事件,发布者触发事件后监听者自动执行。
- 适配器模式:手机充电器,将220V电压转换为手机需要的5V。Spring MVC中,不同的Controller类型(注解式、接口式)通过适配器统一调用。
- 责任链模式:公司报销流程,员工提交申请,先经组长审批,再到经理,再到财务。Spring拦截器链就是请求依次经过多个拦截器处理。
- 前端控制器模式:公司前台,所有来访客人先找前台,前台再引导到具体部门。
DispatcherServlet就是前台,统一接收请求,分发给Controller。 - 原型模式:每次去复印店复印同一份文件,每次拿到的复印件都是新的。
prototype作用域的Bean每次获取都是新实例。 - 装饰器模式:给蛋糕加上奶油、水果,但蛋糕本身还是蛋糕。Spring的
BeanDefinitionDecorator就是在原有Bean定义上添加额外信息。 - 建造者模式:点餐时,你可以一步一步定制汉堡(加肉、加酱、加蔬菜),最后拿到成品。
BeanDefinitionBuilder让你逐步构建Bean定义。 - 桥接模式:遥控器和电视,不同品牌的遥控器可以控制不同品牌的电视。Spring的
View和ViewResolver可以自由组合。 - 组合模式:文件夹里可以包含文件和其他文件夹。
CompositeCacheManager可以包含多个缓存管理器,统一操作。
【扩展知识点详解】
-
单例模式的实现细节
Spring通过DefaultSingletonBeanRegistry中的ConcurrentHashMap(singletonObjects)缓存单例Bean,保证每个名称对应唯一实例。单例Bean需注意线程安全问题,因为多个线程可能同时访问。 - 工厂模式的层次
- 简单工厂:
BeanFactory根据名称返回实例。 - 工厂方法:
FactoryBean接口允许自定义创建逻辑,如SqlSessionFactoryBean用于创建MyBatis的SqlSessionFactory。 - 抽象工厂:
ApplicationContext本身可视为抽象工厂,负责创建不同配置来源的容器。
- 简单工厂:
-
代理模式的选择
Spring AOP默认根据目标类是否实现接口选择代理方式:有接口则JDK动态代理,否则CGLIB。可通过<aop:aspectj-autoproxy proxy-target-class="true"/>或Spring Boot的spring.aop.proxy-target-class=true强制使用CGLIB。 -
模板方法模式的扩展
JdbcTemplate内部封装了获取连接、创建语句、处理结果集、异常处理、关闭资源等固定流程,将变化的SQL、参数设置、结果提取通过回调接口(如PreparedStatementCallback、RowCallbackHandler)实现。 - 策略模式的更多实例
InstantiationStrategy:Bean实例化策略,有CglibSubclassingInstantiationStrategy(支持方法注入)和SimpleInstantiationStrategy(普通反射)。PlatformTransactionManager:事务管理器策略,如DataSourceTransactionManager、JpaTransactionManager。
- 观察者模式的进阶
- 除了内置事件,开发者可自定义事件(继承
ApplicationEvent)和监听器(实现ApplicationListener或使用@EventListener)。 - 事件发布通过
ApplicationEventPublisher(通常是ApplicationContext)完成。 - 支持异步监听(配合
@Async)。
- 除了内置事件,开发者可自定义事件(继承
- 适配器模式的应用
HandlerAdapter有多个实现:RequestMappingHandlerAdapter(处理@RequestMapping方法)、HttpRequestHandlerAdapter(处理HttpRequestHandler)、SimpleControllerHandlerAdapter(处理旧式Controller接口)。AdvisorAdapter将MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice适配为MethodInterceptor。
- 责任链模式的流程
- 拦截器链:
HandlerExecutionChain包含多个HandlerInterceptor,按顺序执行preHandle(返回false可中断)、postHandle(在Controller之后、视图渲染之前)、afterCompletion(请求完成之后)。 - AOP通知链:
ReflectiveMethodInvocation递归调用多个MethodInterceptor,实现环绕通知的链式调用。
- 拦截器链:
-
前端控制器的职责
DispatcherServlet初始化时会加载HandlerMapping、HandlerAdapter、ViewResolver等组件。请求流程:接收请求 → 通过HandlerMapping获取HandlerExecutionChain→ 通过HandlerAdapter执行处理器 → 返回ModelAndView→ 通过ViewResolver解析视图 → 渲染视图。 -
原型模式的使用场景
原型Bean适用于状态ful的场景,如每个用户独有的购物车。Spring通过反射创建新实例,不会缓存。 - 装饰器模式的源码示例
org.springframework.context.annotation.ConfigurationClassPostProcessor内部使用BeanDefinitionDecorator增强配置类。org.springframework.web.util.ContentCachingRequestWrapper装饰请求,缓存内容以便多次读取。
- 建造者模式的更多例子
EmbeddedDatabaseBuilder用于构建嵌入式数据库(如H2)。UriComponentsBuilder构建URI。
-
桥接模式的理解
View接口负责渲染模型数据,ViewResolver负责根据视图名解析出View实例。两者独立发展,可以通过配置组合使用。 -
组合模式的应用
CompositeCacheManager实现了CacheManager接口,内部持有多个CacheManager,调用其方法时会遍历所有子管理器。CompositeClassLoader也类似。 - 设计模式在Spring源码中的位置
可通过阅读Spring源码包名或类名推测模式,例如:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry→ 单例org.springframework.beans.factory.FactoryBean→ 工厂方法org.springframework.aop.framework.ProxyFactory→ 代理org.springframework.jdbc.core.JdbcTemplate→ 模板方法org.springframework.transaction.PlatformTransactionManager→ 策略
注解
【问题】 Spring的常用注解有哪些?@RestController和@Controller的区别?@Component 和 @Bean 的区别是什么?将一个类声明为Spring的 bean 的注解有哪些?@PathVariable 和 @RequestParam 的区别是什么?读取配置文件的注解有哪些?
【参考答案】 Spring框架提供了丰富的注解,用于简化配置、依赖注入、Web开发、事务管理等。以下是针对各子问题的详细解答:
- Spring常用注解概览
按功能分类如下:
- 声明Bean的注解:
@Component、@Service、@Repository、@Controller、@RestController、@Configuration、@Bean - 依赖注入注解:
@Autowired、@Resource、@Inject、@Qualifier、@Primary - 配置相关注解:
@Configuration、@ComponentScan、@PropertySource、@PropertySources、@Import、@ImportResource - 属性绑定注解:
@Value、@ConfigurationProperties - MVC相关注解:
@RequestMapping及其派生注解(@GetMapping、@PostMapping等)、@PathVariable、@RequestParam、@RequestBody、@ResponseBody、@RestController、@ControllerAdvice、@ExceptionHandler - 事务注解:
@Transactional - AOP注解:
@Aspect、@Before、@After、@Around等 - 测试注解:
@SpringBootTest、@MockBean等
- 声明Bean的注解:
- @RestController和@Controller的区别
- @Controller:传统Spring MVC控制器注解,用于标记类为控制器。通常与视图解析器配合返回页面(如JSP、Thymeleaf)。若方法需要返回JSON/XML数据,需在方法上额外添加
@ResponseBody。 - @RestController:从Spring 4引入,是
@Controller和@ResponseBody的组合注解。它表示该类中所有处理器方法的返回值默认直接写入HTTP响应体(即返回JSON/XML),无需在每个方法上加@ResponseBody。适用于RESTful API开发。
- @Controller:传统Spring MVC控制器注解,用于标记类为控制器。通常与视图解析器配合返回页面(如JSP、Thymeleaf)。若方法需要返回JSON/XML数据,需在方法上额外添加
- @Component 和 @Bean 的区别
- 作用对象不同:
@Component是类级别注解,标注在类上,通过类路径扫描自动检测并注册为Bean。@Bean是方法级别注解,通常写在@Configuration类中,方法返回值被注册为Bean。 - 使用场景不同:
@Component适用于自定义编写的类,可通过注解直接标记。@Bean适用于第三方类库或需要复杂初始化逻辑的Bean,因为无法在第三方类上添加@Component,故通过@Bean方法显式创建。 - 实现方式不同:
@Component通过组件扫描(@ComponentScan)自动装配;@Bean通过显式定义方法生成Bean,可灵活控制实例化过程。
- 作用对象不同:
- 将一个类声明为Spring的Bean的注解有哪些
- @Component:通用注解,任何Spring管理的Bean都可使用。
- @Service:标注业务层组件。
- @Repository:标注数据访问层组件,并提供持久化异常转换。
- @Controller:标注控制器层组件。
- @RestController:标注RESTful控制器。
- @Configuration:标注配置类,该类中通过
@Bean声明Bean。 - @Bean:在配置类中显式声明Bean(方法级别)。
- 此外,符合JSR-250规范的
@ManagedBean和JSR-330规范的@Named也可用于声明Bean(需配合相应扫描)。
- @PathVariable 和 @RequestParam 的区别
- @PathVariable:用于获取URI模板中的变量,即URL路径中的占位符。例如
/users/{id},通过@PathVariable("id")获取路径中的id值。 - @RequestParam:用于获取请求参数(查询参数或表单参数),即URL中
?后面的参数。例如/users?id=123,通过@RequestParam("id")获取id值。可设置required(是否必需)和defaultValue(默认值)。
- @PathVariable:用于获取URI模板中的变量,即URL路径中的占位符。例如
- 读取配置文件的注解有哪些
- @Value:用于将配置文件中的属性值注入到字段、方法参数或构造函数参数中,支持SpEL表达式(如
${my.property})。 - @ConfigurationProperties:将配置文件中的属性绑定到Java Bean中,支持前缀绑定和类型安全配置。需配合
@EnableConfigurationProperties或@Component使用。 - @PropertySource:指定要加载的配置文件(如
.properties文件),常与@Configuration搭配,结合@Value或@ConfigurationProperties使用。 - @PropertySources:用于指定多个
@PropertySource。 - 在Spring Boot中,还有
@TestPropertySource(测试环境)、@ConfigurationPropertiesScan等。
- @Value:用于将配置文件中的属性值注入到字段、方法参数或构造函数参数中,支持SpEL表达式(如
【大白话解释于举例说明】
- @RestController vs @Controller:就像餐厅服务员。
@Controller是堂食服务员,你点菜后,他跑去厨房拿菜,然后端到你桌上(返回页面)。而@RestController是外卖员,他直接在厨房把菜打包好(JSON数据)递给你,不用再摆盘上桌。 - @Component vs @Bean:
@Component好比“自动报名”,你在自己身上贴个标签,Spring就自动把你登记在册。@Bean好比“手动推荐”,你写一个方法,在方法里new一个对象,然后告诉Spring:“这是我创建好的对象,你帮我保管”。 - @PathVariable vs @RequestParam:
@PathVariable是从路径里抠数字,比如/user/123,抠出123。@RequestParam是从问号后面拿参数,比如/user?id=123,拿到123。 - 读取配置文件:
@Value就像你去食堂打饭,直接告诉师傅“我要2两米饭”,师傅根据要求打饭。@ConfigurationProperties就像你拿着一个饭盒(配置类),把师傅打的各种菜(配置项)全部装进去,统一带回去。
【扩展知识点详解】
-
@Component的派生性
@Service、@Repository、@Controller都是@Component的派生注解,它们的功能相同,但用于不同分层,便于代码语义化和AOP切面指定(如事务管理针对@Repository)。 -
@Bean与@Configuration
@Bean方法所在的类通常需标注@Configuration,以确保方法间调用返回的是同一个单例Bean(通过CGLIB代理)。若标注在普通@Component类上,则为“Lite模式”,方法调用每次都会新建实例。 - @Autowired与@Resource的区别
@Autowired是Spring注解,默认按类型装配,可通过@Qualifier按名称装配。@Resource是JSR-250注解,默认按名称装配,名称可通过name指定,找不到再按类型。
-
@Qualifier和@Primary
当存在多个同类型Bean时,@Qualifier指定具体名称,@Primary指定首选Bean,解决歧义。 -
@RequestMapping派生注解
Spring 4.3引入了@GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping,简化路由配置。 - @RequestParam常用属性
value/name:参数名称。required:是否必需,默认true。defaultValue:默认值,当参数缺失或值为空时使用。
- @PathVariable常用属性
value/name:路径变量名称。required:是否必需,默认true。
- @Value的用法
- 注入普通字符串:
@Value("张三") - 注入配置文件属性:
@Value("${my.name}") - 注入SpEL表达式:
@Value("#{systemProperties['user.name']}")
- 注入普通字符串:
- @ConfigurationProperties详解
- 需配合
prefix指定前缀,如@ConfigurationProperties(prefix = "my.app")。 - 支持松散绑定(如
my-app-name映射到myAppName)。 - 支持JSR-303校验(配合
@Validated)。 - 需要将配置类注册为Bean(通过
@Component或@EnableConfigurationProperties)。
- 需配合
- @PropertySource的使用
- 加载自定义properties文件:
@PropertySource("classpath:config.properties") - 可指定多个文件,支持占位符。
- 从Spring 4.2起支持
@PropertySources组合多个。
- 加载自定义properties文件:
-
Spring Boot中的配置文件加载顺序
Spring Boot自动加载application.properties/yml,也支持通过@PropertySource加载自定义文件。@TestPropertySource用于测试环境覆盖。 - @ControllerAdvice与@RestControllerAdvice
@ControllerAdvice用于全局异常处理、数据绑定等,可结合@ExceptionHandler、@InitBinder、@ModelAttribute。@RestControllerAdvice=@ControllerAdvice+@ResponseBody,返回JSON格式的异常信息。
-
常用组合注解
@SpringBootApplication=@Configuration+@EnableAutoConfiguration+@ComponentScan。 -
元注解与组合注解
Spring许多注解是元注解组合而成,例如@RestController由@Controller和@ResponseBody组成。 - 注解的扫描配置
通过@ComponentScan指定扫描包,可包含或排除特定注解类。Spring Boot中@SpringBootApplication默认扫描主类所在包及子包。
【问题】 常用的参数校验注解有哪些?
【参考答案】
Java Bean Validation规范(JSR 303/JSR 380)定义了一套注解,用于对Java对象属性进行声明式校验。Spring框架集成了该规范,并提供了与Spring MVC的整合。以下是最常用的校验注解(均位于javax.validation.constraints包下,推荐使用标准注解而非Hibernate扩展):
- 空值检查
@Null:被注释的元素必须为null@NotNull:被注释的元素必须不为null@NotEmpty:被注释的字符串、集合、数组或Map不能为null,且长度/大小必须大于0@NotBlank:被注释的字符串不能为null,且去除首尾空格后长度必须大于0(只能用于字符串)
- 布尔检查
@AssertTrue:被注释的元素必须为true(通常用于Boolean类型)@AssertFalse:被注释的元素必须为false
- 数值范围检查
@Min(value):被注释的元素必须是数字,且值 ≥ 指定的最小值@Max(value):被注释的元素必须是数字,且值 ≤ 指定的最大值@DecimalMin(value):被注释的元素必须是数字,且值 ≥ 指定的最小值(支持字符串表示的浮点数)@DecimalMax(value):被注释的元素必须是数字,且值 ≤ 指定的最大值@Negative:被注释的元素必须是负数(JSR 380新增)@NegativeOrZero:被注释的元素必须是负数或零@Positive:被注释的元素必须是正数@PositiveOrZero:被注释的元素必须是正数或零
- 大小检查
@Size(max, min):被注释的元素的大小必须在指定范围内(可用于字符串、集合、数组、Map)
- 字符串格式检查
@Pattern(regexp, flags):被注释的字符串必须符合指定的正则表达式@Email:被注释的字符串必须是有效的电子邮件格式(JSR 380支持,之前为Hibernate扩展)
- 日期/时间检查
@Past:被注释的日期必须是一个过去的日期@PastOrPresent:被注释的日期必须是过去或现在(JSR 380新增)@Future:被注释的日期必须是一个将来的日期@FutureOrPresent:被注释的日期必须是将来或现在
- 其他
@Digits(integer, fraction):被注释的元素必须是数字,且整数位数和小数位数在指定范围内
在Spring中启用校验
- 对于
@RequestBody参数,使用@Valid(JSR标准)或@Validated(Spring扩展)触发校验,校验失败抛出MethodArgumentNotValidException。 - 对于
@PathVariable、@RequestParam等非Bean参数,需要在类上标注@Validated,然后在参数上直接使用校验注解,校验失败抛出ConstraintViolationException。
示例:
@RestController
@Validated // 必须添加以启用方法参数校验
public class UserController {
@PostMapping("/user")
public User createUser(@RequestBody @Valid User user) { // 校验请求体
return user;
}
@GetMapping("/user/{id}")
public User getUser(@PathVariable @Min(1) @Max(100) Long id) { // 校验路径变量
return userService.getById(id);
}
}
【大白话解释于举例说明】
- @NotNull:就像“必须带身份证”,不能没有。
- @NotBlank:不仅要有身份证,而且身份证上的名字不能是空格(至少得有个字)。
- @NotEmpty:就像吃饭的碗,碗不能没有(null),也不能空着(size=0)。
- @Min/@Max:比如年龄必须≥18且≤60,就像“只接待18-60岁顾客”。
- @Pattern:手机号必须符合“1开头的11位数字”的格式。
- @Email:输入的字符串必须看起来像邮箱(有@和域名)。
- @Past:生日必须是过去的日子,不能是未来。
- @Future:预约时间必须是将来的。
- @Size:密码长度必须在6-20位之间,太短或太长都不行。
- @AssertTrue:比如同意条款必须勾选(true)。
【扩展知识点详解】
- JSR 303/380 与 Hibernate Validator
- JSR 303(Bean Validation 1.0)定义了基础注解,JSR 380(Bean Validation 2.0)增加了新注解(如
@Email、@Positive等)。 - Hibernate Validator是JSR的参考实现,并提供了一些扩展注解(如
@Length、@Range、@URL),但不推荐使用,以便于切换实现。
- JSR 303(Bean Validation 1.0)定义了基础注解,JSR 380(Bean Validation 2.0)增加了新注解(如
- @Valid 与 @Validated 的区别
@Valid是JSR标准注解,支持嵌套校验(即校验对象内部的字段)。@Validated是Spring提供的注解,是@Valid的变体,支持分组校验(通过groups属性指定校验组),但不支持嵌套校验(嵌套需配合@Valid)。- 在Controller中,通常对
@RequestBody使用@Valid;对方法参数(如@RequestParam)校验,需在类上加@Validated。
-
分组校验(Groups)
有时同一个实体在不同场景下需要不同校验规则(如新增时ID必填,更新时ID可选)。可通过定义分组接口(空接口),在注解中指定groups,并在@Validated中指定激活的分组。 - 自定义校验注解
通过组合已有注解或自定义逻辑实现。步骤:- 定义注解,元注解
@Constraint(validatedBy = YourValidator.class)。 - 实现
ConstraintValidator接口编写校验逻辑。 - 在注解中指定默认消息(message)、分组等。
- 定义注解,元注解
- 校验失败异常处理
@RequestBody校验失败:抛出MethodArgumentNotValidException,可通过@ControllerAdvice+@ExceptionHandler统一处理。- 方法参数校验失败:抛出
ConstraintViolationException。 - 可自定义响应格式,返回友好的错误信息。
-
国际化消息
校验消息可以通过ValidationMessages.properties文件自定义,Spring也支持MessageSource整合。 -
手动触发校验
可以通过Validator接口手动校验对象:validator.validate(obj)。 -
Spring Boot自动配置
Spring Boot在spring-boot-starter-validation中自动引入Hibernate Validator,并配置好校验器。无需额外配置即可使用。 -
嵌套校验
如果对象属性是另一个复杂对象,需要在属性上加上@Valid才能递归校验。 -
性能考虑
校验通常发生在请求进入Controller时,对于高并发场景需注意校验开销。复杂校验可考虑异步或精简规则。 - 注意事项
- 对于
@RequestParam、@PathVariable等参数校验,必须使用@Validated(类级别)并直接在参数上标注注解,否则校验不生效。 @NotNull与@NotBlank的区别:@NotBlank会自动去除空格,比@NotEmpty更严格。- 数值类型推荐使用包装类型(如
Integer),以便null能被@NotNull检测。
- 对于
生命周期
【问题】 介绍一下Spring Bean的生命周期?
【参考答案】 Spring Bean的生命周期是指Bean从创建到销毁的整个过程,由Spring IoC容器进行管理。完整的生命周期分为以下几个阶段:
-
实例化(Instantiation) 容器根据Bean定义(BeanDefinition)通过反射(或CGLIB)创建Bean的实例。此时Bean还是一个“空”对象,属性尚未赋值。
-
属性赋值(Populate Properties) Spring根据依赖注入配置(如XML、注解)对Bean的属性进行填充,包括通过setter方法、构造器或字段注入。
- 初始化(Initialization)
初始化阶段包含一系列回调,使Bean能够感知容器并执行自定义初始化逻辑。具体步骤顺序如下:
- Aware接口回调:如果Bean实现了特定的Aware接口,容器会调用相应的方法注入容器资源。常见的Aware接口有:
BeanNameAware:传入Bean在容器中的名称。BeanClassLoaderAware:传入加载Bean的类加载器。BeanFactoryAware:传入当前BeanFactory实例。ApplicationContextAware:传入ApplicationContext(若容器为ApplicationContext)。
- BeanPostProcessor前置处理:调用所有注册的
BeanPostProcessor的postProcessBeforeInitialization()方法,允许对Bean进行加工(如生成代理)。 - 初始化方法调用:
- 如果Bean实现了
InitializingBean接口,调用其afterPropertiesSet()方法。 - 如果配置了自定义的
init-method(如@Bean(initMethod = "init")),调用该方法。
- 如果Bean实现了
- BeanPostProcessor后置处理:调用
BeanPostProcessor的postProcessAfterInitialization()方法,通常用于生成最终代理对象(如AOP)。
- Aware接口回调:如果Bean实现了特定的Aware接口,容器会调用相应的方法注入容器资源。常见的Aware接口有:
-
使用中(In Use) Bean已准备就绪,可以被应用程序使用。此时Bean驻留在容器中,直到容器关闭或被显式销毁。
- 销毁(Destruction)
当容器关闭时,如果Bean是单例(singleton)且需要销毁,会执行以下销毁回调:
- 如果Bean实现了
DisposableBean接口,调用其destroy()方法。 - 如果配置了自定义的
destroy-method(如@Bean(destroyMethod = "close")),调用该方法。
- 如果Bean实现了
注意:对于原型(prototype)作用域的Bean,Spring容器只负责创建和初始化,不负责销毁,客户端需自行管理。
【大白话解释于举例说明】 将Spring Bean的生命周期比作一个人的一生:
- 实例化:婴儿出生(刚创建对象,但还没有名字、身份)。
- 属性赋值:父母给婴儿取名、穿衣服(设置属性)。
- Aware接口:婴儿开始认识自己是谁(BeanNameAware),认识自己的家庭(BeanFactoryAware),认识周围环境(ApplicationContextAware)。
- BeanPostProcessor前置处理:出门前家人帮忙整理衣服、检查物品(前置处理)。
- 初始化方法:举行成人礼,正式宣告成年(afterPropertiesSet/init-method),可以开始工作。
- BeanPostProcessor后置处理:穿上工作服,佩戴徽章(后置处理,如AOP代理增强)。
- 使用中:成年人正常工作,为社会创造价值。
- 销毁:老年退休,办理后事(DisposableBean/destroy-method),生命结束。
对于原型Bean,就像临时工,干完活就走,公司不负责他的后续(销毁由自己负责)。
【扩展知识点详解】
-
BeanPostProcessor的作用
BeanPostProcessor是Spring的扩展点,允许在所有Bean初始化前后进行自定义处理(如包装代理、属性修改)。多个BeanPostProcessor可通过Ordered接口排序。常见的实现有AutowiredAnnotationBeanPostProcessor(处理@Autowired)和AbstractAutoProxyCreator(AOP代理创建)。 - Aware接口体系
Spring提供了多种Aware接口,用于注入容器相关对象:BeanNameAwareBeanClassLoaderAwareBeanFactoryAwareApplicationContextAware(需要容器为ApplicationContext)EnvironmentAwareResourceLoaderAwareMessageSourceAwareApplicationEventPublisherAware等
这些接口通常用于框架内部组件,普通业务Bean应避免使用,以降低对Spring的耦合。
- InitializingBean和DisposableBean
InitializingBean:只有一个afterPropertiesSet()方法,在属性赋值后、自定义init-method之前调用。DisposableBean:只有一个destroy()方法,在容器关闭时调用。
使用这些接口会使得Bean与Spring API耦合,推荐使用@PostConstruct和@PreDestroy(JSR-250注解)或自定义init/destroy方法。
-
@PostConstruct和@PreDestroy
JSR-250规范定义的注解,更优雅且不依赖Spring。它们分别对应初始化后和销毁前回调,在Spring中由CommonAnnotationBeanPostProcessor处理。执行顺序:@PostConstruct→afterPropertiesSet()→init-method。 - 初始化方法多种方式的执行顺序
若同时存在,顺序为:@PostConstruct标注的方法InitializingBean.afterPropertiesSet()init-method指定的方法
销毁顺序类似:@PreDestroy→DisposableBean.destroy()→destroy-method。
- 作用域对生命周期的影响
- singleton:Bean由容器管理完整生命周期,包括实例化、初始化、销毁。
- prototype:容器负责实例化和初始化,但销毁回调(
@PreDestroy、DisposableBean)不会执行,因为容器不持有其引用,需由客户端自行清理。 - request/session/application(Web作用域):生命周期与Web请求相关,销毁时机由容器管理。
-
FactoryBean的特殊性
FactoryBean本身是一个Bean,用于创建其他Bean。其生命周期与普通Bean类似,但通过getObject()返回的对象不受容器完整生命周期管理(除非被标记为单例且容器管理)。 -
循环依赖与三级缓存
在单例Bean的创建过程中,Spring通过三级缓存解决循环依赖。实例化后的Bean会提前暴露(放入三级缓存),允许其他Bean引用尚未初始化完成的Bean,但此时的Bean仅完成实例化,属性尚未填充。 - Bean的完整流程(包含循环依赖)
- 实例化(通过构造器)
- 提前暴露(放入三级缓存)
- 属性赋值(可能依赖其他Bean)
- 初始化(Aware → BeanPostProcessor前置 → 初始化方法 → BeanPostProcessor后置)
- 放入一级缓存(成品)
- 实际应用
理解Bean生命周期有助于:- 在合适的阶段进行自定义扩展(如实现
BeanPostProcessor)。 - 排查依赖注入问题(如属性未赋值)。
- 理解AOP代理的生成时机(在
BeanPostProcessor后置阶段)。 - 合理使用初始化/销毁回调管理资源(如线程池、连接池的关闭)。
- 在合适的阶段进行自定义扩展(如实现
【问题】 分析一下Spring各个模块?
【参考答案】 Spring框架采用模块化设计,根据功能划分为多个独立的模块,开发者可以根据需求选择使用。以下基于Spring 5.x版本介绍主要模块及其作用:
- 核心容器(Core Container)
- spring-core:框架底层基础,提供资源访问、类型转换、常用工具类等核心功能。
- spring-beans:支持控制反转(IoC)和依赖注入(DI),包含BeanFactory接口及其实现,负责Bean的定义、加载、实例化和依赖管理。
- spring-context:在core和beans基础上构建,提供ApplicationContext(IoC容器的高级实现),并添加了国际化(MessageSource)、事件传播(ApplicationEvent)、资源加载、数据绑定、校验、以及Java EE集成等企业级功能。
- spring-expression:提供强大的表达式语言(SpEL),支持在运行时查询和操作对象图,可用于属性赋值、方法调用等。
- AOP与Aspects
- spring-aop:提供面向切面编程的实现,支持JDK动态代理和CGLIB,允许将横切关注点(如日志、事务)与业务逻辑分离。
- spring-aspects:集成AspectJ框架,提供更强大、更灵活的AOP功能(如编译时织入、加载时织入),通过注解或XML配置即可使用AspectJ切面。
- 数据访问/集成
- spring-jdbc:简化JDBC编程,提供JdbcTemplate等模板类,消除重复代码,并统一异常处理。
- spring-tx:提供声明式和编程式事务管理,支持本地事务和分布式事务,可与各种持久化技术(JDBC、ORM)集成。
- spring-orm:集成主流ORM框架(如Hibernate、JPA、MyBatis),提供一致的异常转换和事务管理。
- spring-oxm:支持对象与XML之间的映射(Object/XML Mapping),集成JAXB、Castor、XStream等实现。
- spring-jms:简化Java消息服务(JMS)的编程,提供JmsTemplate等工具,支持消息生产者和消费者的异步通信。
- Web模块
- spring-web:提供基础的Web集成功能,如多文件上传、HTTP客户端(RestTemplate)、Servlet监听器等,以及与其他Web框架(如JSF)的集成。
- spring-webmvc:基于Servlet API的MVC框架,提供控制器、视图解析、处理器映射等组件,支持RESTful风格的开发。
- spring-websocket:支持WebSocket通信,实现全双工、低延迟的浏览器与服务器交互。
- spring-webflux:响应式Web框架,基于Reactive Streams,支持非阻塞、事件驱动的编程模型(Spring 5引入)。
- 消息通信
- spring-messaging:作为消息抽象模块,提供对消息协议(如STOMP)的支持,为WebSocket和响应式编程提供基础。
- 测试
- spring-test:支持单元测试和集成测试,提供与JUnit、TestNG的集成,以及Mock对象、事务测试、Web测试等功能。
- 其他模块
- spring-context-support:提供对第三方库的集成支持,如缓存(Ehcache)、任务调度(Quartz)、邮件发送(JavaMail)等。
- spring-instrument:提供类加载时的增强支持,用于特定应用服务器。
注意:早期的spring-web-struts模块在Spring 3.x后已废弃,因为Struts 1.x已停止维护,Struts 2.x可通过其他方式集成。现代开发中,Spring Boot和Spring Cloud进一步简化了模块的依赖管理。
【大白话解释于举例说明】 把Spring比作一个多功能的工具箱,每个模块就是不同功能的工具层:
- core/beans/context:工具箱的基础框架和架子(核心容器),负责摆放和管理工具(Bean)。你告诉它需要什么工具,它自动给你拿出来,不用自己满仓库找。
- AOP:像给工具加上自动记录功能的贴膜(切面),比如给螺丝刀贴上“每次使用后自动计数”的标签,不用在螺丝刀本身写代码。
- 数据访问模块:专门处理数据库的工具套件:
jdbc:简化JDBC,像一把万能扳手,拧螺丝(操作数据库)变得简单。tx:自动帮你管好“交易记录”(事务),比如你要转账,它保证钱要么同时转出和转入,要么都不转。orm:让你用Hibernate/MyBatis这些高级工具,不用自己造轮子。
- Web模块:搭建网站的工具:
webmvc:像一个接待员(DispatcherServlet),把客户请求(HTTP)分配给对应的处理员(Controller)。websocket:像对讲机,浏览器和服务器可以随时通话。webflux:像超级接待员,能同时处理成千上万请求而不堵塞。
- 测试模块:自带质检工具,确保组装好的工具(应用)没问题。
总之,Spring模块化设计让你可以根据项目需要,只拿必要的工具,轻装上阵。
【扩展知识点详解】
- 模块依赖关系
spring-core和spring-beans是所有模块的基础,其他模块都直接或间接依赖它们。spring-context依赖spring-core、spring-beans、spring-aop、spring-expression等。spring-webmvc依赖spring-web,而spring-web依赖spring-context。
-
Spring Boot与模块关系
Spring Boot基于Spring Framework,通过起步依赖(starter)封装了常用模块的组合。例如spring-boot-starter-web包含了spring-webmvc、spring-web、spring-boot-starter-tomcat等,简化配置。 - 模块的JAR命名与Maven坐标
Spring模块的JAR包命名通常为spring-模块名,如spring-core。Maven坐标示例:<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.30</version> </dependency> - 可选模块与集成
spring-orm与具体ORM框架(如Hibernate)搭配时,需额外引入ORM框架本身的依赖。spring-oxm需配合具体的OXM实现库使用。
- 模块演进
- Spring 4.x引入
spring-websocket模块,支持WebSocket和STOMP。 - Spring 5.x引入
spring-webflux模块,提供响应式编程支持。 - 旧版
spring-struts已在Spring 3.2.x中标记为废弃,后续版本移除。
- Spring 4.x引入
- 模块功能扩展
Spring通过spring-context-support模块集成多种第三方技术,如:- 缓存:
EhCacheCacheManager、RedisCacheManager - 调度:
Quartz、TaskScheduler - 邮件:
JavaMailSender
- 缓存:
- 测试模块的高级特性
@ContextConfiguration加载Spring上下文。@Transactional和@Rollback用于数据库测试自动回滚。MockMvc模拟HTTP请求测试Controller。
- 模块化设计的优点
- 按需引入,避免依赖臃肿。
- 各模块职责清晰,便于维护和扩展。
- 可替换性:如事务模块可独立用于非Spring环境。
bean
【问题】 Spring中的bean的作用域有哪些?单例bean的线程安全问题了解吗?
【参考答案】 Spring Bean的作用域(Scope)定义了Bean的生命周期和可见范围。Spring框架支持多种作用域,其中常用的有:
- 标准作用域(在任何Spring环境中可用)
- singleton(单例):默认作用域。每个Spring IoC容器中只存在一个Bean实例,所有对该Bean的请求都返回同一个对象。适用于无状态的服务层、数据访问层等。
- prototype(原型):每次请求(注入或通过
getBean()获取)都会创建一个新的Bean实例。Spring不管理原型Bean的完整生命周期,销毁回调需要由客户端自行处理。适用于有状态的Bean,如每个用户独立的购物车。
- Web作用域(仅在Web-aware的ApplicationContext中可用)
- request:每个HTTP请求创建一个新的Bean实例,仅在当前请求内有效。请求结束后Bean被销毁。基于
RequestContextListener或RequestContextFilter实现。 - session:每个HTTP会话(Session)创建一个Bean实例,在整个会话周期内有效。不同会话拥有不同实例。
- application:整个ServletContext生命周期内创建一个Bean实例,类似于单例但作用域在ServletContext级别。在Spring 5中引入,也可使用
global session(在Portlet环境中)。 - websocket:每个WebSocket会话创建一个Bean实例(Spring 4+引入,5+完善)。
- request:每个HTTP请求创建一个新的Bean实例,仅在当前请求内有效。请求结束后Bean被销毁。基于
- 自定义作用域
开发者可实现
Scope接口并注册到容器,以支持自定义作用域(如线程作用域、任务作用域)。
单例Bean的线程安全问题
单例Bean由于在整个容器中只有一个实例,当多个线程同时访问该Bean时,如果Bean中存在可变的成员变量(状态),并且对这些变量进行写操作,就可能出现线程安全问题(数据不一致、脏读等)。例如,一个单例的服务类中有一个int count字段,多个线程同时调用增加count的方法,就会导致竞态条件。
原因:Spring容器默认不会对单例Bean进行同步处理,也不保证线程安全。线程安全与否完全取决于Bean本身的实现。
解决方案:
- 无状态Bean:尽量将Bean设计为无状态,即不包含可变的成员变量。所有需要的状态通过方法参数传入。这是最佳实践,例如Controller、Service通常应该是无状态的。
- 使用ThreadLocal:如果必须保存线程隔离的状态,可以使用
ThreadLocal将变量与当前线程绑定,每个线程持有自己的副本,避免共享。 - 同步控制:对可变状态的访问加锁(如
synchronized或Lock),但会降低并发性能,不推荐。 - 改用原型作用域:如果Bean需要持有用户特有的状态,可以将其作用域改为
prototype或request/session,确保每个线程或请求有自己的实例。 - 使用不可变对象:将成员变量设为
final,或使用只读对象,避免修改。
需要注意的是,即使Bean是无状态的,如果它调用了线程不安全的第三方组件(如非线程安全的集合),也可能引发问题。
【大白话解释于举例说明】
- 作用域比喻:
- singleton:公司只有一个饮水机,所有人都用同一个。适合大家共享的东西(如配置信息)。
- prototype:公司有一次性纸杯,每次喝水都拿一个新杯子。适合每个人独立使用的东西(如一次性餐具)。
- request:餐厅里,每个顾客(请求)有自己的菜单(Bean),用完就收走。适合存储本次请求的数据(如表单对象)。
- session:健身房会员卡,每个会员(会话)有自己的储物柜(Bean),会籍期间一直有效。适合存储用户登录信息。
- 线程安全问题:假设饮水机(单例Bean)上有一个计数器,记录总共被用过多少次。如果多个人同时去按计数器(并发写),数字可能不准(比如少记了)。解决办法:
- 无状态:把计数器去掉,每次记录用笔写在本子上(作为参数传入方法),不共享状态。
- ThreadLocal:给每个人发一个私人记事本,自己记自己的次数(线程隔离)。
- 同步:给饮水机加一把锁,一次只能一个人用(性能差)。
【扩展知识点详解】
- 作用域的实现原理
- singleton:Bean实例被缓存到
singletonObjects(ConcurrentHashMap)中。 - prototype:容器不缓存,每次请求都通过反射创建新实例。
- request/session:通过
RequestContextHolder将当前请求/会话绑定到ThreadLocal中,Bean实例存储在请求或会话的属性中。需在web.xml配置RequestContextListener或RequestContextFilter。
- singleton:Bean实例被缓存到
-
单例Bean的线程安全误区
很多人误以为Spring单例Bean是线程安全的,实际上Spring只是管理Bean的生命周期,并不负责同步。常见的线程安全类如JdbcTemplate、HibernateTemplate等本身就是线程安全的,因为它们是无状态的。 - @Scope注解的使用
@Component @Scope("prototype") public class MyPrototypeBean { ... }在Web环境中,可使用
@RequestScope、@SessionScope、@ApplicationScope等组合注解。 -
原型Bean的销毁
原型Bean的销毁不由Spring容器管理,但可以通过自定义DestructionAwareBeanPostProcessor或@PreDestroy(实际上不生效)来处理。通常无需关心原型Bean销毁,除非持有需要释放的资源(如连接池)。 -
作用域依赖问题
如果将短作用域Bean(如request)注入到长作用域Bean(如singleton)中,会因Bean创建时机不同导致问题。解决方案:使用@Lazy代理(<aop:scoped-proxy/>),Spring会生成一个代理对象,每次调用时从当前作用域获取真实Bean。 - 线程安全的其他解决方案
- 原子变量:如
AtomicInteger,适用于简单计数。 - 并发集合:如
ConcurrentHashMap代替普通HashMap。 - 不可变对象:对象一旦创建就不变,天然线程安全。
- 函数式编程:避免共享状态。
- 原子变量:如
-
测试中的注意事项
测试单例Bean时,由于上下文会缓存Bean,多次测试可能共享状态,需注意重置状态或使用@DirtiesContext。 -
Spring Boot中的作用域
Spring Boot自动配置的Bean大多是单例且无状态的,如RestTemplate、ObjectMapper。若需要自定义作用域,可通过@Bean配合@Scope实现。 - 线程作用域(自定义)
可以通过实现Scope接口创建线程作用域,使Bean在每个线程中保持单例,常与ThreadLocal配合,用于异步任务中传递上下文。
循环依赖
【问题】 Spring是怎样解决循环依赖的?
【参考答案】
Spring容器通过三级缓存机制解决单例Bean之间的循环依赖问题。循环依赖是指两个或多个Bean相互引用,例如A依赖B,B依赖A,形成闭环。Spring只能解决单例作用域下通过setter注入或字段注入的循环依赖,对于构造器注入的循环依赖无法解决(会抛出BeanCurrentlyInCreationException)。
三级缓存的结构 Spring内部维护了三个缓存Map,用于存储不同阶段的Bean实例:
- 一级缓存(
singletonObjects):存放已经完成初始化的单例Bean(成品Bean)。 - 二级缓存(
earlySingletonObjects):存放提前暴露的、尚未完成初始化的Bean实例(半成品Bean),用于解决循环依赖。 - 三级缓存(
singletonFactories):存放Bean的ObjectFactory,用于生成提前暴露的Bean实例。当Bean需要提前暴露时,会将一个工厂对象放入三级缓存,后续通过该工厂获取实例。
解决循环依赖的流程(以A依赖B,B依赖A为例)
- 开始创建A:
getBean(A),先从一级缓存查找,不存在,且A正在创建中,于是准备创建A。 - 实例化A:调用构造器创建A的原始对象(此时属性尚未填充),并将其包装为
ObjectFactory放入三级缓存(singletonFactories)。 - 填充A的属性:发现A依赖B,开始
getBean(B)。 - 创建B:同样先查缓存,不存在,则实例化B,并将B的工厂放入三级缓存。
- 填充B的属性:发现B依赖A,调用
getBean(A)。- 先查一级缓存:没有。
- 查二级缓存:没有。
- 查三级缓存:找到A的工厂,通过工厂获取A的早期引用(半成品),并将该引用放入二级缓存(
earlySingletonObjects),同时删除三级缓存中的工厂。
- B获取到A的引用:B完成属性填充,接着执行初始化步骤(如
BeanPostProcessor),最终将B放入一级缓存,并删除二级和三级缓存中的B。 - B创建完成:回到A的创建过程,此时A已经能从二级缓存或通过
getBean(B)获取到B的完整实例(B已在一级缓存),A完成属性填充和初始化,然后放入一级缓存。
关键点
- 三级缓存中的
ObjectFactory允许在需要时生成早期引用,如果Bean需要被AOP代理,工厂返回的是代理对象而非原始对象,从而保证依赖注入的是代理对象。 - 二级缓存存储早期引用,用于避免重复从三级缓存获取工厂创建对象。
- 只有单例Bean且允许循环引用(默认允许)时才会使用三级缓存。原型Bean无法解决循环依赖,因为容器不缓存原型Bean。
【大白话解释于举例说明】 可以把Spring容器想象成一个婚介所,要介绍对象A和B,但A说“我要B才结婚”,B说“我要A才结婚”,这成了死循环。婚介所怎么办呢?
- 一级缓存:已领证的新人(完整Bean)。
- 二级缓存:订婚但还没领证的(半成品Bean)。
- 三级缓存:婚介所掌握的“准新人资料卡”(ObjectFactory),可以随时约见面。
流程:
- A来登记,婚介所先看已领证的没有,于是给A拍照(实例化),并把资料卡(工厂)放入三级缓存。
- A说“我要B才结婚”,婚介所去找B。
- B来登记,同样拍照,资料卡放三级缓存。
- B说“我要A才结婚”,婚介所查已领证没有,查订婚没有,然后从三级缓存找到A的资料卡,打电话让A来见面(获取早期引用),A虽然还没领证,但可以先订婚(放入二级缓存)。
- B见到A后,满意,于是领证(完成初始化),放入一级缓存。
- 然后婚介所通知A:“B已经领证了,你们可以继续”,A得到B的完整信息,也完成领证,放入一级缓存。
这样,通过“先订婚再领证”的方式,解决了死循环。如果A或B要求必须领完证才能见面(构造器注入),那就没法解决了。
【扩展知识点详解】
-
为什么需要三级缓存,两级不够吗?
两级缓存(一级+二级)理论上可以解决普通循环依赖,但无法处理AOP代理的情况。因为AOP代理对象需要在Bean初始化最后阶段(BeanPostProcessor后置处理)才生成,而早期暴露的必须是代理对象(否则注入的是原始对象,导致代理失效)。三级缓存通过ObjectFactory允许在暴露早期引用时根据情况返回原始对象或代理对象,实现了延迟生成代理的能力。如果只用两级缓存,则必须在实例化后立即创建代理,破坏了AOP的正常流程。 - 哪些循环依赖无法解决?
- 构造器注入:因为构造器在实例化阶段就必须传入依赖,此时Bean尚未创建,无法提前暴露。
- 原型作用域:原型Bean不缓存,无法提前暴露。
- 非单例作用域(如request、session):作用域范围导致无法缓存。
@Async注解的Bean:由于@Async通过代理实现,且代理创建时机特殊,可能导致循环依赖异常。
-
循环依赖的检测
Spring在创建Bean时,会将当前正在创建的Bean名称放入一个“正在创建集合”(singletonsCurrentlyInCreation)中。当检测到递归依赖时,会判断是否允许循环引用(setAllowCircularReferences),默认允许。如果不允许,则抛出异常。 - 三级缓存的具体代码位置
DefaultSingletonBeanRegistry类中定义了三个缓存:/** 一级缓存:成品Bean */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** 二级缓存:早期暴露的半成品Bean */ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); /** 三级缓存:Bean工厂,用于生成早期引用 */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
- 解决构造器循环依赖的方案
- 改用setter注入或字段注入。
- 使用
@Lazy注解,延迟加载其中一个依赖,例如在构造器参数上加@Lazy,使其通过代理延迟初始化。 - 重新设计类结构,避免循环依赖。
-
循环依赖与AOP
当Bean需要AOP代理时,三级缓存中的ObjectFactory会返回代理对象(通过SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法)。这样早期暴露的就是代理对象,保证了后续注入的正确性。 -
三级缓存与性能
三级缓存中的工厂只在需要时才调用,避免了不必要的代理创建。二级缓存则缓存了工厂生成的早期引用,防止多次调用工厂导致重复创建。 -
如何禁用循环依赖
可以通过设置AbstractAutowireCapableBeanFactory的setAllowCircularReferences(false)来禁止循环依赖,这样一旦检测到循环引用就会抛出异常。 - 常见面试题
- “Spring如何解决循环依赖?”:核心是三级缓存。
- “为什么需要三级缓存而不是两级?”:因为AOP需要延迟生成代理对象。
- “构造器循环依赖能解决吗?”:不能,因为构造器调用时Bean还未实例化。
理解Spring解决循环依赖的原理,有助于深入掌握IoC容器的内部工作机制,并在实际开发中避免不合理的设计。

事务
【问题】 事务的特性是什么?Spring 管理事务的方式有几种?
【参考答案】 事务(Transaction)是数据库操作的基本单元,具有四大特性(ACID):
- 原子性(Atomicity):事务是一个不可分割的工作单元,事务中的所有操作要么全部成功,要么全部失败回滚。如果事务执行过程中发生错误,已执行的操作会被撤销,回到事务开始前的状态。
- 一致性(Consistency):事务执行前后,数据库的完整性约束(如主键、外键、唯一性约束等)必须保持一致。即事务将数据库从一个一致状态转换到另一个一致状态。
- 隔离性(Isolation):多个事务并发执行时,彼此之间应该相互隔离,避免互相干扰。隔离性通过不同隔离级别来控制并发事务的可见性,防止脏读、不可重复读、幻读等问题。
- 持久性(Durability):事务一旦提交,其对数据库的修改就是永久性的,即使系统发生故障也不会丢失。
Spring 管理事务的方式主要有两种:
- 编程式事务:通过编写代码手动管理事务,例如使用
TransactionTemplate或PlatformTransactionManager直接控制事务的开启、提交、回滚。这种方式灵活但代码侵入性强,实际开发中较少使用。 - 声明式事务:基于AOP实现,通过XML配置或注解(如
@Transactional)来声明事务边界。开发者只需在方法或类上添加注解,Spring会自动在方法调用前后进行事务管理,无需编写额外代码。声明式事务降低了耦合度,是推荐的使用方式。
【大白话解释于举例说明】
- 事务的ACID特性:想象你去银行转账,从A账户扣100元,给B账户加100元。
- 原子性:要么两个操作都成功(转账完成),要么都失败(钱没动)。不能只扣A不加B。
- 一致性:转账前后,两个账户的总余额不变(比如都是1000元),且满足账户不能为负等业务规则。
- 隔离性:如果你和另一人同时操作同一个账户,系统会通过锁等机制避免数据混乱。比如你转账的同时,别人查询余额,应该看到的是转账前或转账后的稳定状态,而不是中间状态。
- 持久性:转账成功后,即使银行系统突然断电,重启后钱也不会丢。
- Spring事务管理方式:
- 编程式事务:就像你自己写一份详细的流程说明:先开启事务,执行操作,如果成功就提交,失败就回滚。代码里到处都是try-catch和commit/rollback。
- 声明式事务:就像你在方法上贴个标签“@Transactional”,告诉Spring这个方法需要事务管理,Spring会在背后帮你自动处理开启、提交、回滚,你只需专注业务逻辑。
【扩展知识点详解】
- 事务的ACID深入理解
- 原子性由事务日志(undo log)保证,回滚时利用日志恢复数据。
- 一致性由应用层和数据库约束共同保证。
- 隔离性由锁机制和MVCC实现,不同隔离级别对应不同的并发控制策略。
- 持久性通过redo log保证,事务提交时日志刷盘,即使内存数据丢失也能恢复。
- Spring事务管理的核心接口
- PlatformTransactionManager:Spring事务抽象的核心接口,定义了获取事务、提交、回滚等方法。常见实现类有
DataSourceTransactionManager(JDBC/MyBatis)、HibernateTransactionManager、JpaTransactionManager等。 - TransactionDefinition:定义事务的属性,包括隔离级别、传播行为、超时时间、只读标志等。
- TransactionStatus:表示当前事务的状态,可用于编程式事务中控制回滚等。
- PlatformTransactionManager:Spring事务抽象的核心接口,定义了获取事务、提交、回滚等方法。常见实现类有
-
声明式事务的实现原理
基于AOP,通过@Transactional注解(或XML配置)标记需要事务管理的方法。Spring会为目标Bean创建代理对象,在方法调用前后通过TransactionInterceptor拦截器织入事务逻辑。当方法正常返回时提交事务,抛出异常时根据配置决定是否回滚。 - @Transactional注解的常用属性
- propagation:事务传播行为,如
REQUIRED(默认)、REQUIRES_NEW、SUPPORTS等。 - isolation:事务隔离级别,如
READ_COMMITTED、REPEATABLE_READ等。 - timeout:事务超时时间(秒),默认-1(使用底层数据库的超时)。
- readOnly:是否为只读事务,用于优化(如JDBC的只读提示)。
- rollbackFor:指定触发回滚的异常类型,默认运行时异常(
RuntimeException)和错误(Error)回滚,受检异常(Exception)不回滚。 - noRollbackFor:指定不触发回滚的异常类型。
- propagation:事务传播行为,如
- 事务传播行为详解
传播行为定义了事务方法被另一个事务方法调用时,事务如何传递。常见传播行为:- REQUIRED:支持当前事务,如果不存在则新建事务(默认)。
- SUPPORTS:支持当前事务,如果不存在则以非事务方式执行。
- MANDATORY:必须存在当前事务,否则抛出异常。
- REQUIRES_NEW:新建事务,如果当前存在事务则挂起当前事务。
- NOT_SUPPORTED:以非事务方式执行,如果当前存在事务则挂起。
- NEVER:以非事务方式执行,如果当前存在事务则抛出异常。
- NESTED:嵌套事务,基于保存点实现,允许部分回滚。 传播行为总结表: | 传播行为 | 当前存在事务 | 当前无事务 | 特点 | |—————-|————–|————–|——————————| | REQUIRED | 加入 | 新建 | 默认,最常用 | | REQUIRES_NEW | 挂起当前,新建 | 新建 | 独立事务,互不影响 | | SUPPORTS | 加入 | 非事务运行 | 可选事务 | | NOT_SUPPORTED | 挂起当前 | 非事务运行 | 强制非事务 | | MANDATORY | 加入 | 抛异常 | 必须已有事务 | | NEVER | 抛异常 | 非事务运行 | 禁止事务 | | NESTED | 嵌套事务 | 新建 | 基于保存点,可部分回滚 |
示例详解:
-
REQUIRED(默认) 行为:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。 适用场景:大多数业务操作,需要保证在同一个事务中完成。 示例: ```java @Service public class UserService { @Autowired private AccountService accountService;
@Transactional(propagation = Propagation.REQUIRED) public void register(User user) { // 保存用户 userDao.save(user); // 调用账户服务创建账户(该方法也使用REQUIRED) accountService.createAccount(user.getId()); } }
@Service public class AccountService { @Transactional(propagation = Propagation.REQUIRED) public void createAccount(Long userId) { accountDao.create(userId); // 如果这里抛出异常,整个register事务回滚 } }
**结果**:`createAccount`方法加入到`register`的事务中,两者在同一事务中。任何地方抛出异常都会导致整个事务回滚。
52. REQUIRES_NEW
**行为**:无论当前是否存在事务,都创建一个新事务。如果当前存在事务,则将当前事务挂起,新事务独立提交或回滚,不影响原事务。
**适用场景**:需要独立提交的操作,如记录日志、审计,即使主事务回滚也不应影响。
**示例**:
```java
@Service
public class OrderService {
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
public void placeOrder(Order order) {
orderDao.save(order);
// 记录日志(使用独立事务)
logService.log("Order placed: " + order.getId());
// 模拟异常
if (true) throw new RuntimeException("订单创建失败");
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String message) {
logDao.insert(message);
}
}
结果:placeOrder方法抛出异常后,订单保存回滚,但logService.log方法已经在独立事务中提交,日志被持久化。因为REQUIRES_NEW会挂起主事务,新事务独立提交。
- SUPPORTS
行为:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
适用场景:查询方法,有事务时可利用事务一致性,无事务时也可执行。
示例:
@Transactional(propagation = Propagation.SUPPORTS) public User findUser(Long id) { return userDao.findById(id); }结果:如果调用方有事务,则加入;否则以非事务方式执行。
- NOT_SUPPORTED
行为:以非事务方式执行,如果当前存在事务,则挂起当前事务。
适用场景:执行不需要事务的操作,如发送短信、邮件,避免长事务占用连接。
示例:
@Service public class NotificationService { @Transactional(propagation = Propagation.NOT_SUPPORTED) public void sendEmail(String to, String content) { // 发送邮件(无需事务) mailSender.send(to, content); } } - MANDATORY
行为:必须在一个已有的事务中执行,否则抛出异常。
适用场景:强制要求在事务内执行的操作,如数据修改。
示例:
@Transactional(propagation = Propagation.MANDATORY) public void updateUser(User user) { userDao.update(user); }结果:如果调用该方法时没有事务,则抛出
IllegalTransactionStateException。 - NEVER
行为:必须以非事务方式执行,如果当前存在事务,则抛出异常。
适用场景:不允许在事务中执行的操作,如某些只读操作或可能引起死锁的操作。
示例:
@Transactional(propagation = Propagation.NEVER) public void generateReport() { // 生成报表,无需事务 } - NESTED
行为:如果当前存在事务,则在嵌套事务内执行;如果没有事务,则行为类似
REQUIRED。嵌套事务基于数据库保存点(savepoint)实现,允许部分回滚。 适用场景:需要部分回滚的场景,如复杂的批量处理,某一条失败不影响整体。 示例:@Service public class BatchService { @Transactional(propagation = Propagation.REQUIRED) public void processBatch(List<Item> items) { for (Item item : items) { try { processItem(item); // 该方法使用NESTED } catch (Exception e) { // 记录失败项,继续处理其他 } } } @Transactional(propagation = Propagation.NESTED) public void processItem(Item item) { itemDao.save(item); if (item.isInvalid()) { throw new RuntimeException("无效项"); } } }结果:
processItem在嵌套事务中执行,如果某个项无效,仅回滚该项的操作,不影响外层事务中已保存的其他项。注意:嵌套事务需要底层数据库支持保存点(如MySQL InnoDB支持)。 - 编程式事务的两种实现方式
- 使用TransactionTemplate:通过回调接口执行事务代码,由模板管理事务。
@Autowired private TransactionTemplate transactionTemplate; public void doSomething() { transactionTemplate.execute(status -> { // 业务操作 return result; }); } - 使用PlatformTransactionManager:直接获取事务状态,手动commit/rollback。
@Autowired private PlatformTransactionManager transactionManager; public void doSomething() { DefaultTransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def); try { // 业务操作 transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); throw e; } }
- 使用TransactionTemplate:通过回调接口执行事务代码,由模板管理事务。
- 声明式事务的注意事项
@Transactional注解只能应用于public方法上,因为代理机制只能拦截public方法。- 同一个类中的方法调用(如A方法调用B方法,两者都有
@Transactional)会导致事务失效,因为调用不经过代理对象。解决方案:注入自身代理或使用AopContext.currentProxy()。 - 事务回滚默认仅对运行时异常(
RuntimeException)和Error生效,对受检异常(如IOException)不回滚。可通过rollbackFor属性配置。 - 合理设置
readOnly可提高性能,但仅对某些数据库有效(如MySQL的InnoDB在只读事务中可优化)。 - 隔离级别需要底层数据库支持,如MySQL默认可重复读(REPEATABLE_READ),Oracle默认读已提交(READ_COMMITTED)。
- 事务隔离级别与并发问题
- 脏读:一个事务读到另一个事务未提交的数据。
- 不可重复读:一个事务内两次读取同一行数据,结果不一致(因其他事务修改并提交)。
- 幻读:一个事务内两次查询(范围)得到不同行数(因其他事务插入或删除)。
- 隔离级别从低到高:READ_UNCOMMITTED(可能脏读)→ READ_COMMITTED(避免脏读)→ REPEATABLE_READ(避免脏读、不可重复读)→ SERIALIZABLE(避免所有并发问题,但性能低)。
隔离级别详解(以MySQL InnoDB为例)
- READ_UNCOMMITTED(读未提交)
行为:最低级别,允许脏读,即可能读到未提交的数据。
问题:可能导致脏读、不可重复读、幻读。
示例:
@Transactional(isolation = Isolation.READ_UNCOMMITTED) public void readUncommittedDemo() { // 事务A:读取账户余额 BigDecimal balance = accountDao.getBalance(1L); // 此时事务B可能正在修改余额但未提交,balance可能是脏数据 } - READ_COMMITTED(读已提交)
行为:一个事务只能读到其他事务已提交的数据,避免了脏读,但可能出现不可重复读和幻读。
问题:不可重复读(同一事务内两次读取可能不同)、幻读。
示例:
@Transactional(isolation = Isolation.READ_COMMITTED) public void readCommittedDemo() { // 第一次读取 BigDecimal balance1 = accountDao.getBalance(1L); // 此时事务B修改了余额并提交 // 第二次读取 BigDecimal balance2 = accountDao.getBalance(1L); // balance1 != balance2,发生不可重复读 } - REPEATABLE_READ(可重复读)
行为:确保同一事务中多次读取同一行数据结果一致(通过行锁或MVCC),避免了脏读和不可重复读,但可能出现幻读(MySQL InnoDB通过间隙锁在一定程度上避免幻读)。
问题:可能幻读(但InnoDB在可重复读级别下使用间隙锁,基本避免了幻读)。
示例:
@Transactional(isolation = Isolation.REPEATABLE_READ) public void repeatableReadDemo() { // 第一次范围查询 List<Account> list1 = accountDao.findByBalanceGreaterThan(100); // 此时事务B插入一条余额大于100的记录并提交 // 第二次范围查询 List<Account> list2 = accountDao.findByBalanceGreaterThan(100); // InnoDB通过间隙锁阻止了幻读,list1和list2相同(但理论上可能不同) } - SERIALIZABLE(可串行化)
行为:最高级别,通过强制事务串行执行,完全避免脏读、不可重复读、幻读,但并发性能最低。
示例:
@Transactional(isolation = Isolation.SERIALIZABLE) public void serializableDemo() { // 所有操作都会被锁定,直到事务提交 accountDao.updateBalance(1L, newBalance); }
隔离级别对比表 | 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 | |—————–|——|————|——|——————————| | READ_UNCOMMITTED| 可能 | 可能 | 可能 | 极少使用,数据一致性差 | | READ_COMMITTED | 避免 | 可能 | 可能 | Oracle默认,大多数场景可接受 | | REPEATABLE_READ | 避免 | 避免 | 可能 | MySQL默认,InnoDB通过间隙锁避免幻读 | | SERIALIZABLE | 避免 | 避免 | 避免 | 完全串行,性能低 |
选择隔离级别的建议
- 一般情况下使用数据库默认级别(如MySQL的REPEATABLE_READ,Oracle的READ_COMMITTED)即可。
- 对一致性要求极高且并发较低的场景可考虑SERIALIZABLE。
- 通过应用层锁或乐观锁(如版本号)来替代高隔离级别,以平衡性能。
注意:隔离级别的设置依赖于底层数据库的支持,Spring只是将隔离级别传递给数据库连接,具体实现由数据库完成。
- 事务的超时属性
@Transactional(readOnly = false, timeout = -1)
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为-1,这表示事务的超时时间取决于底层事务系统或者没有超时时间。 对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性; 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持
-
Spring事务与数据库事务的关系
Spring事务是对数据库事务的抽象和扩展。实际事务控制由底层数据库连接完成,Spring通过事务管理器协调多个数据源事务(如JTA分布式事务),但通常使用本地事务。 -
事务的回滚规则 1:这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。 2:当
@Transactional注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。 3:如果你想要回滚你定义的特定的异常类型的话,可以这样:@Transactional(rollbackFor= MyException.class)4:被@Transactional注解的方法所在的类必须被 Spring 管理,否则不生效; 5:底层使用的数据库必须支持事务机制,否则不生效; -
常见问题
- 事务失效场景:方法非public、自调用、异常被捕获未抛出、数据源未配置事务管理器、传播行为配置不当等。
- 大事务问题:事务内包含耗时操作或大量数据操作,会导致长事务,引发锁竞争、连接池耗尽等问题。应尽量缩小事务范围。
- 分布式事务:跨多个数据库或服务的事务,需使用JTA、TCC、Seata等方案,Spring提供了
JtaTransactionManager支持。
【问题】 详细讲一下@Transactional注解可以吗?
【参考答案】
@Transactional是Spring框架中用于声明式事务管理的核心注解,它基于AOP实现,能够将事务管理逻辑从业务代码中分离出来,大大简化了数据库事务的开发。以下从多个维度详细介绍:
一. 作用范围
- 方法级别:最推荐的使用方式。将注解标注在具体的
public方法上,仅对该方法生效。注意:注解只能作用于public方法,否则事务不会生效(Spring代理机制限制)。 - 类级别:标注在类上,表示该类中所有
public方法都启用事务管理。此时事务属性(如传播行为、隔离级别等)对所有方法统一适用,但方法级别注解可以覆盖类级别的配置。 - 接口级别:不推荐使用。因为Spring代理默认基于接口(JDK动态代理)或类(CGLIB),但注解在接口上时,只有当使用接口代理且方法也在接口中声明时才会生效,容易产生混乱,建议避免。
二. 常用配置参数
| 参数 | 说明 | 默认值 | 示例 |
|——|——|——–|——|
| propagation | 事务传播行为,定义事务方法被调用时事务的创建策略 | Propagation.REQUIRED | @Transactional(propagation = Propagation.REQUIRES_NEW) |
| isolation | 事务隔离级别,解决并发事务的读问题 | Isolation.DEFAULT(使用数据库默认级别) | @Transactional(isolation = Isolation.REPEATABLE_READ) |
| timeout | 事务超时时间(秒),超过该时间未完成则自动回滚 | -1(无超时) | @Transactional(timeout = 30) |
| readOnly | 是否为只读事务,可用于优化(如底层数据库可能跳过锁) | false | @Transactional(readOnly = true) |
| rollbackFor | 指定触发回滚的异常类型,默认仅运行时异常(RuntimeException)和Error回滚 | {} | @Transactional(rollbackFor = Exception.class) |
| rollbackForClassName | 同上,使用异常类名指定 | {} | @Transactional(rollbackForClassName = {"java.lang.Exception"}) |
| noRollbackFor | 指定不触发回滚的异常类型 | {} | @Transactional(noRollbackFor = BusinessException.class) |
| noRollbackForClassName | 同上,使用类名 | {} | @Transactional(noRollbackForClassName = {"com.example.BusinessException"}) |
三. 工作原理
@Transactional基于Spring AOP实现,核心流程如下:
- 代理对象的创建:当Spring容器启动时,会为标注了
@Transactional的类(或方法)生成代理对象。若目标类实现了接口,默认使用JDK动态代理;否则使用CGLIB生成子类代理。 - 事务拦截器:代理对象的方法调用会被
TransactionInterceptor拦截。该拦截器通过PlatformTransactionManager获取事务状态,并根据注解配置执行事务逻辑。 - 执行流程:
- 方法执行前:根据传播行为决定是开启新事务、加入现有事务还是挂起当前事务。
- 方法执行中:通过
invocation.proceed()调用目标方法。 - 方法执行后:若无异常,则提交事务;若出现异常,则根据
rollbackFor配置决定是否回滚事务。
- 事务提交/回滚:最终由具体的
PlatformTransactionManager实现(如DataSourceTransactionManager)执行数据库的提交或回滚操作。
四. 事务失效的常见场景
- 方法非
public:Spring AOP只能拦截public方法,若注解在private、protected或默认方法上,事务不生效。 - 同一类中方法调用:如A方法调用B方法(两者都在同一个类中,且B有
@Transactional),此时调用不经过代理对象,事务不生效。可通过注入自身代理(AopContext.currentProxy())或拆分类解决。 - 异常被捕获未抛出:若方法内捕获了异常且未抛出,事务拦截器无法感知异常,就不会回滚。
- 传播行为配置不当:如内层方法使用
Propagation.NOT_SUPPORTED,外层事务会被挂起,内层方法以非事务方式执行。 - 数据源未配置事务管理器:如果没有配置
PlatformTransactionManager的Bean,事务注解无效。 - 使用了不支持事务的存储引擎:如MySQL的MyISAM引擎不支持事务,即使注解也不会生效。
- 代理方式问题:如果使用JDK动态代理,但目标类没有实现接口,则无法生成代理对象,事务失效(需强制使用CGLIB)。
【大白话解释于举例说明】
可以把@Transactional想象成给一个操作贴上了“官方授权”的标签。贴上这个标签后,Spring就会像一个尽职的管家,在你执行操作前自动打开一个“工作日志”(开启事务),操作过程中如果一切顺利,就帮你在日志上盖章确认(提交事务);如果操作中出了问题(抛出异常),管家就会把日志撕掉,让你之前的所有操作都像没发生过一样(回滚事务)。
- 作用范围:你可以在一个班级(类)的所有同学(方法)上都贴标签,也可以只给某个同学贴。但注意,只有公开课(public方法)才需要管家服务,私下小动作(非public)管家看不到。
- 配置参数:就像你可以给管家指定不同的服务细则。比如:
propagation:告诉管家遇到别人已经在记账时,是加入别人的账本(REQUIRED),还是另开一个新账本(REQUIRES_NEW)。isolation:告诉管家记账时要注意隐私,防止别人偷看未完成的内容(隔离级别)。timeout:规定这个账必须在30分钟内记完,超时就不记了。rollbackFor:指定什么类型的错误必须撕账本,比如遇到“金额错误”这种严重问题必须回滚,而“备注写错”这种小问题可以忽略。
- 失效场景:
- 如果同一个班里的小明(方法)在班里内部偷偷叫小红帮忙(自调用),没有经过管家,那么小红的事务标签就失效了。
- 如果小明在记账过程中自己把错误掩盖了(捕获异常没抛出),管家不知道出了问题,就不会回滚。
【扩展知识点详解】
@Transactional的底层依赖- Spring事务抽象的核心接口:
PlatformTransactionManager、TransactionDefinition、TransactionStatus。 @Transactional本质是TransactionDefinition的一种声明式表达,通过TransactionInterceptor将其转化为事务操作。
- Spring事务抽象的核心接口:
- 不同事务管理器的选择
DataSourceTransactionManager:适用于单数据源的JDBC、MyBatis等。HibernateTransactionManager:适用于Hibernate。JpaTransactionManager:适用于JPA。JtaTransactionManager:适用于分布式事务(JTA)。
Spring Boot会根据依赖自动配置合适的事务管理器。
-
事务传播行为的源码解析
Propagation枚举定义了7种行为,在AbstractPlatformTransactionManager的handleExistingTransaction等方法中处理。例如REQUIRES_NEW会挂起当前事务,创建新事务,提交或回滚后恢复原事务。 -
隔离级别的实现依赖
Spring只是将隔离级别传递给数据库连接,最终由数据库的锁机制和MVCC实现。例如MySQL的REPEATABLE_READ通过间隙锁防止幻读,而Oracle的READ_COMMITTED通过快照读避免脏读。 -
只读事务的优化
readOnly=true会向底层数据库传递只读提示,某些数据库(如MySQL InnoDB)可以优化查询,避免加锁。但注意,如果实际执行了写操作,数据库会抛出异常。 -
回滚规则的详细逻辑
默认情况下,事务只在遇到运行时异常(RuntimeException)和Error时回滚,而受检异常(如IOException)不会触发回滚。这是因为受检异常通常代表可预期的业务问题,而非系统级错误。可通过rollbackFor强制指定回滚。 @Transactional的自调用失效解决方案- 方法一:通过
ApplicationContext.getBean()获取自身代理,再调用方法。 - 方法二:使用
AopContext.currentProxy()(需配置exposeProxy=true)。 - 方法三:将事务方法拆分到另一个Service类中,通过依赖注入调用。
- 方法一:通过
-
事务的嵌套与保存点
Propagation.NESTED使用数据库保存点(savepoint)实现部分回滚。当事务回滚到保存点时,仅撤销保存点后的操作,外层事务仍可继续。注意:需要底层数据库支持保存点(如MySQL InnoDB)。 -
Spring事务与多线程
事务是与线程绑定的(通过ThreadLocal实现),因此在多线程环境下,子线程中的操作默认不在父线程的事务中。可通过编程式事务或手动传递事务上下文实现。 -
测试中的事务行为
Spring测试框架提供了@Transactional注解(在spring-test中),用于测试方法的事务回滚,默认测试完成后回滚,避免污染数据库。 - 常见误区
- 误区:
@Transactional可以保证所有数据库操作都在一个事务中。实际上,如果涉及多个数据源,需要使用分布式事务。 - 误区:方法内调用其他
@Transactional方法一定会加入同一事务。取决于传播行为,REQUIRES_NEW会新建独立事务。 - 误区:
readOnly=true一定能提高性能。对于某些数据库,可能没有实际优化效果,甚至增加额外开销。
- 误区:
- 最佳实践
- 在Service层的方法上使用
@Transactional,而非DAO层。 - 尽量缩小事务范围,避免在事务中执行远程调用、IO操作等耗时任务。
- 合理设置
rollbackFor,确保预期异常也能触发回滚。 - 对于仅查询的方法,可设置
readOnly=true,但需确认实际效果。
- 在Service层的方法上使用
springmvc
【问题】 Spring MVC的作用是什么?优点有哪些?工作流程是什么?主要组件有哪些?
【参考答案】 Spring MVC是Spring框架基于Servlet API构建的Web层框架,它实现了经典的Model-View-Controller(MVC)设计模式,旨在简化Web应用程序的开发,提供高度灵活、可扩展的架构。
一、Spring MVC的作用
Spring MVC的主要作用是将Web层进行职责分离,通过将输入逻辑、业务逻辑和显示逻辑解耦,使应用程序更易于开发和维护。它作为前端控制器(DispatcherServlet)接收所有请求,协调不同的组件处理请求并生成响应,从而简化了基于Java的Web开发。
二、Spring MVC的优点
- 角色清晰:框架明确了控制器、验证器、命令对象、模型对象、分发器、处理器映射、视图解析器等角色的职责,便于开发人员理解和分工。
- 与Spring无缝集成:作为Spring家族的一部分,可以直接利用Spring的IoC容器、AOP、事务管理等特性,实现依赖注入和横切关注点的统一管理。
- 灵活的URL映射:通过注解(如
@RequestMapping及其派生注解)或XML配置,可以灵活地将URL映射到控制器方法,支持RESTful风格。 - 强大的数据绑定与验证:支持将HTTP请求参数自动绑定到Java对象的属性,并与JSR-303/JSR-380 Bean Validation集成,简化了数据校验。
- 多种视图技术支持:可与JSP、Thymeleaf、FreeMarker、Velocity等视图技术无缝集成,同时支持JSON、XML等数据格式输出。
- 开箱即用的功能:内置文件上传、国际化、主题解析、异常处理等功能,减少重复开发。
- 测试友好:框架组件可轻松进行单元测试,例如通过
MockMvc模拟HTTP请求测试控制器。 - 高度可扩展:通过实现
HandlerMapping、HandlerAdapter、ViewResolver等接口,可自定义扩展框架行为。
三、工作流程(执行步骤) Spring MVC处理请求的典型流程如下(以传统Servlet容器为例):
- 客户端发送请求:用户通过浏览器或客户端发送HTTP请求,请求到达
DispatcherServlet(前端控制器)。 - 查找处理器:
DispatcherServlet根据请求信息(URL、HTTP方法等)调用HandlerMapping(处理器映射器),找到匹配的处理器(Handler)和拦截器链。处理器通常是一个控制器(Controller)的方法。 - 调用处理器:
DispatcherServlet通过HandlerAdapter(处理器适配器)执行处理器方法。HandlerAdapter负责适配不同类型的处理器(如基于注解的@Controller方法、实现Controller接口的旧式控制器等)。 - 执行处理器方法:控制器方法执行业务逻辑,处理请求参数,调用服务层,最后返回一个
ModelAndView对象(包含模型数据和视图名)。 - 视图解析:
DispatcherServlet将ModelAndView对象交给ViewResolver(视图解析器),根据视图名解析出具体的View对象。 - 视图渲染:
View对象负责将模型数据填充到视图中,生成最终的响应内容(如HTML、JSON等)。 - 返回响应:
DispatcherServlet将渲染后的结果通过HTTP响应返回给客户端。
注:如果控制器方法直接返回@ResponseBody或ResponseEntity,则跳过视图解析步骤,由HandlerAdapter直接将结果写入响应体。
四、主要组件及其作用
| 组件 | 作用 | 说明/常见实现 |
|——|——|—————|
| DispatcherServlet | 前端控制器,Spring MVC的核心。负责接收所有请求,协调其他组件完成请求处理。 | 配置在web.xml或通过Servlet 3.0+初始化,是框架的入口。 |
| HandlerMapping | 处理器映射器,根据请求查找对应的处理器(Handler)和拦截器链。 | 常见实现:RequestMappingHandlerMapping(基于注解)、SimpleUrlHandlerMapping(基于URL配置)。 |
| HandlerAdapter | 处理器适配器,帮助DispatcherServlet调用不同类型的处理器,屏蔽具体处理器的差异。 | 常见实现:RequestMappingHandlerAdapter(处理@RequestMapping方法)、HttpRequestHandlerAdapter(处理HttpRequestHandler)、SimpleControllerHandlerAdapter(处理旧式Controller接口)。 |
| Controller | 处理器,也称页面控制器,包含具体的业务逻辑。负责处理请求,返回ModelAndView或直接写入响应。 | 通常使用@Controller注解标识,方法上使用@RequestMapping等注解映射URL。 |
| ViewResolver | 视图解析器,根据逻辑视图名解析出具体的View对象。 | 常见实现:InternalResourceViewResolver(解析JSP)、ThymeleafViewResolver、BeanNameViewResolver等。 |
| View | 视图,负责渲染模型数据,生成最终输出(HTML、JSON等)。 | 常见实现:JstlView、ThymeleafView、MappingJackson2JsonView等。 |
| HandlerInterceptor | 处理器拦截器(非必需),允许在请求处理前后进行自定义处理(如登录检查、日志记录)。 | 实现HandlerInterceptor接口,在HandlerMapping中配置。 |
| MultipartResolver | 文件上传解析器,处理multipart/form-data类型的请求。 | 常见实现:CommonsMultipartResolver(基于Apache Commons FileUpload)、StandardServletMultipartResolver(基于Servlet 3.0+)。 |
| LocaleResolver | 国际化解析器,解析客户端的区域信息,用于国际化。 | 常见实现:AcceptHeaderLocaleResolver、SessionLocaleResolver等。 |
| ThemeResolver | 主题解析器,支持主题切换功能。 | 较少使用,如CookieThemeResolver。 |
| HandlerExceptionResolver | 异常解析器,统一处理控制器抛出的异常。 | 常见实现:ExceptionHandlerExceptionResolver(支持@ExceptionHandler)、DefaultHandlerExceptionResolver等。 |
【大白话解释于举例说明】 可以把Spring MVC比作一家餐厅的运作流程:
- DispatcherServlet:餐厅的前台接待员。所有客人(请求)进门先找他,他负责安排后续服务。
- HandlerMapping:点餐牌(菜单),接待员根据客人点的菜名(URL)找到对应的厨师(控制器)。
- HandlerAdapter:厨房的传菜员,他负责把客人订单(请求)交给厨师,并把做好的菜(模型数据)取回来。
- Controller:厨师,根据订单做菜(执行业务逻辑),做好后放在托盘(
ModelAndView)上。 - ViewResolver:摆盘员,根据厨师说的“用盘子A装菜”(逻辑视图名),找到具体的盘子(视图)。
- View:盘子,负责把菜(模型数据)摆好,呈现给客人。
- HandlerInterceptor:餐厅的监控摄像头,在客人点菜前后记录日志、检查会员权限等。
整个流程:客人(请求)→ 前台接待(DispatcherServlet)→ 看菜单(HandlerMapping)→ 传菜员(HandlerAdapter)→ 厨师(Controller)做菜 → 摆盘员(ViewResolver)找盘子 → 盘子(View)装盘 → 前台把菜端给客人(响应)。
【扩展知识点详解】
-
DispatcherServlet的初始化
Spring MVC在Web容器启动时,通过DispatcherServlet的init()方法初始化WebApplicationContext,并扫描配置或注解装配所有组件(如HandlerMapping、HandlerAdapter等)。Spring Boot则通过自动配置完成。 -
请求处理链中的拦截器
HandlerInterceptor的preHandle()在处理器方法执行前调用,postHandle()在处理器方法执行后、视图渲染前调用,afterCompletion()在整个请求完成后调用(通常用于资源清理)。多个拦截器按配置顺序形成链式调用。 -
基于注解的控制器
使用@Controller和@RequestMapping(及其派生注解)简化开发。@RestController是@Controller和@ResponseBody的组合,用于RESTful服务。 -
数据绑定与类型转换
Spring MVC通过DataBinder将请求参数绑定到JavaBean,并支持自定义PropertyEditor或Converter进行类型转换。@InitBinder可在控制器中自定义数据绑定器。 -
验证机制
配合JSR-303/380 Bean Validation,在控制器方法参数上使用@Valid或@Validated触发验证,验证结果通过BindingResult或Errors对象获取,或抛出异常统一处理。 - 异常处理
- 使用
@ExceptionHandler在控制器内部处理特定异常。 - 使用
@ControllerAdvice或@RestControllerAdvice实现全局异常处理。 - 实现
HandlerExceptionResolver自定义异常解析器。
- 使用
-
RESTful支持
@ResponseBody将返回值直接写入响应体,@RequestBody将请求体转换为对象。ResponseEntity允许设置响应状态码和头信息。 -
异步处理
Spring MVC 3.2+支持基于Callable和DeferredResult的异步请求处理,提高服务器吞吐量。 -
Spring MVC与Spring Boot
Spring Boot通过spring-boot-starter-web自动配置DispatcherServlet、内置Tomcat、提供默认的视图解析器等,简化了部署和配置。 - 常用配置
- 在XML中配置:
<mvc:annotation-driven/>启用注解驱动。 - 在Java配置中:
@EnableWebMvc启用Spring MVC配置,配合实现WebMvcConfigurer自定义。
- 在XML中配置:
【问题】 SpringMVC怎么样设定重定向和转发的?SpringMvc怎么和AJAX相互调用的?
【参考答案】 (1)转发:在返回值前面加”forward:”,譬如”forward:user.do?name=method4” (2)重定向:在返回值前面加”redirect:”,譬如”redirect:http://www.baidu.com” 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象 (1)加入Jackson.jar (2)在配置文件中配置json的映射 (3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。
【问题】 Spring MVC常用的注解有哪些?怎么用?
【参考答案】 Spring MVC提供了一系列注解,用于简化Web层开发,实现请求映射、参数绑定、数据校验、异常处理等功能。以下是常用注解的分类详解及使用示例:
一、控制器声明注解
| 注解 | 作用 | 用法示例 |
|——|——|———-|
| @Controller | 标记类为Spring MVC的控制器,负责处理HTTP请求。 | @Controller public class UserController { ... } |
| @RestController | @Controller和@ResponseBody的组合,类中所有方法默认返回JSON/XML,不进行视图解析。 | @RestController public class UserController { ... } |
| @RequestMapping | 映射HTTP请求到控制器方法,可配置URL、请求方法、参数等。可标注在类或方法上。 | @RequestMapping("/users") public class UserController { @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User get(@PathVariable Long id) { ... } } |
二、请求映射注解(@RequestMapping的派生注解,通常用于方法)
| 注解 | 作用 | 用法示例 |
|——|——|———-|
| @GetMapping | 处理GET请求,等价于@RequestMapping(method = RequestMethod.GET) | @GetMapping("/{id}") public User get(@PathVariable Long id) { ... } |
| @PostMapping | 处理POST请求 | @PostMapping public User create(@RequestBody User user) { ... } |
| @PutMapping | 处理PUT请求 | @PutMapping("/{id}") public User update(@PathVariable Long id, @RequestBody User user) { ... } |
| @DeleteMapping | 处理DELETE请求 | @DeleteMapping("/{id}") public void delete(@PathVariable Long id) { ... } |
| @PatchMapping | 处理PATCH请求 | @PatchMapping("/{id}") public User partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> updates) { ... } |
三、参数绑定注解
| 注解 | 作用 | 用法示例 |
|——|——|———-|
| @PathVariable | 从URL路径模板中获取变量值。 | @GetMapping("/users/{id}") public User get(@PathVariable("id") Long userId) { ... } |
| @RequestParam | 获取请求参数(查询参数或表单参数),支持默认值、必需等属性。 | @GetMapping("/users") public List<User> list(@RequestParam(value = "page", defaultValue = "1") int page) { ... } |
| @RequestBody | 将HTTP请求体(JSON/XML等)绑定到方法参数,通常用于POST/PUT请求。 | @PostMapping("/users") public User create(@RequestBody @Valid User user) { ... } |
| @RequestHeader | 获取请求头中的某个值。 | @GetMapping("/header") public String getHeader(@RequestHeader("User-Agent") String userAgent) { ... } |
| @CookieValue | 获取Cookie中的值。 | @GetMapping("/cookie") public String getCookie(@CookieValue("JSESSIONID") String sessionId) { ... } |
| @ModelAttribute | 用于将请求参数绑定到模型对象,或向模型添加数据。有三种使用方式:
1. 在方法参数上:将请求参数绑定到该对象,并自动添加到模型。
2. 在方法上:该方法会在控制器每个请求方法执行前调用,返回值添加到模型。
3. 在方法上且无返回值,但参数包含Model:手动添加属性。 | @PostMapping("/users") public String create(@ModelAttribute User user) { ... }@ModelAttribute("commonData") public String populateCommon() { return "commonValue"; } |
| @SessionAttributes | 将模型中的指定属性存储到HTTP Session中,用于跨请求共享数据(如表单步骤)。需标注在类上。 | @Controller @SessionAttributes("user") public class UserController { ... } |
| @RequestPart | 用于处理multipart/form-data请求中的文件上传部分,与@RequestParam类似但更适用于文件。 | @PostMapping("/upload") public String handleUpload(@RequestPart("file") MultipartFile file) { ... } |
四、响应相关注解
| 注解 | 作用 | 用法示例 |
|——|——|———-|
| @ResponseBody | 将方法返回值直接写入HTTP响应体(JSON/XML等),不进行视图解析。可标注在方法或类上(配合@Controller)。 | @GetMapping("/users/{id}") @ResponseBody public User get(@PathVariable Long id) { ... } |
| @ResponseStatus | 设置HTTP响应的状态码,可标注在方法或异常类上。 | @ResponseStatus(HttpStatus.CREATED) @PostMapping public User create(@RequestBody User user) { ... } |
五、数据校验注解(与JSR-303/380结合)
| 注解 | 作用 | 用法示例 |
|——|——|———-|
| @Valid | 激活对方法参数的校验,通常与@RequestBody或@ModelAttribute配合。校验失败抛出MethodArgumentNotValidException。 | @PostMapping("/users") public User create(@Valid @RequestBody User user, BindingResult result) { ... } |
| @Validated | Spring对@Valid的扩展,支持分组校验。可标注在类或方法参数上。 | @PostMapping("/users") public User create(@Validated(GroupA.class) @RequestBody User user) { ... } |
六、异常处理与增强注解
| 注解 | 作用 | 用法示例 |
|——|——|———-|
| @ControllerAdvice | 全局控制器增强,可定义全局的@ExceptionHandler、@InitBinder、@ModelAttribute方法。 | @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<String> handle(Exception e) { ... } } |
| @RestControllerAdvice | @ControllerAdvice和@ResponseBody的组合,用于REST API的全局异常处理。 | @RestControllerAdvice public class GlobalRestExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handle(ResourceNotFoundException e) { ... } } |
| @ExceptionHandler | 处理控制器中特定异常的方法,可定义在控制器内或全局@ControllerAdvice中。 | @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<String> handleNotFound(ResourceNotFoundException e) { ... } |
| @InitBinder | 在控制器中自定义数据绑定器,用于注册自定义PropertyEditor或格式化器。 | @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true)); } |
七、其他注解
| 注解 | 作用 | 用法示例 |
|——|——|———-|
| @CrossOrigin | 启用跨域资源共享(CORS),可标注在类或方法上。 | @CrossOrigin(origins = "http://example.com") @GetMapping("/users") public List<User> list() { ... } |
| @MatrixVariable | 从URL矩阵变量中获取值(较少使用)。 | @GetMapping("/cars/{path}") public String get(@MatrixVariable(name = "color", pathVar = "path") String color) { ... } |
示例场景:
@RestController // 外卖员模式
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}") // GET请求,路径参数
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping
public User createUser(@Valid @RequestBody User user) { // 请求体绑定并校验
return userService.save(user);
}
@ExceptionHandler(ResourceNotFoundException.class) // 本店处理特定异常
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException e) {
return new ErrorResponse(e.getMessage());
}
}
@ControllerAdvice // 全局助手
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneric(Exception e) {
return ResponseEntity.status(500).body("系统繁忙,请稍后重试");
}
}
【扩展知识点详解】
@RequestMapping的常用属性value/path:映射的URL路径。method:允许的HTTP方法,如RequestMethod.GET。params:限制请求必须包含某些参数,如params = "myParam=myValue"。headers:限制请求头。consumes:限制请求的Content-Type,如consumes = "application/json"。produces:限制响应的Content-Type,如produces = "application/json"。
@RequestParam的常用属性value/name:参数名称。required:是否必需,默认true。defaultValue:默认值(当参数缺失或值为空时使用)。
@PathVariable的常用属性value/name:路径变量名称。required:是否必需,默认true(路径变量通常必需)。
-
@RequestBody与@ResponseBody的底层
通过HttpMessageConverter实现,Spring会根据请求的Content-Type和响应的Accept头自动选择转换器(如MappingJackson2HttpMessageConverter处理JSON)。 - 数据绑定与校验流程
- 使用
@Valid或@Validated标记参数,Spring会调用Validator进行校验。 - 如果参数后紧跟
BindingResult或Errors参数,则校验结果放入该对象,不会抛出异常;否则校验失败抛出异常。 - 可结合分组校验(
@Validated的groups属性)在不同场景使用不同校验规则。
- 使用
@ModelAttribute的详细用法- 作为方法参数:将请求参数绑定到该对象,并自动添加到模型中,相当于
new User()+ 属性绑定 +model.addAttribute(user)。 - 作为方法级注解:该方法在控制器每个
@RequestMapping方法执行前调用,返回值放入模型(可指定key)。常用于准备下拉列表数据等。
- 作为方法参数:将请求参数绑定到该对象,并自动添加到模型中,相当于
@SessionAttributes的使用注意事项- 必须与
@ModelAttribute配合使用,将模型中的属性提升到Session中。 - 清除Session数据可使用
SessionStatus.setComplete()。 - 注意线程安全问题,Session中的属性可能被多个请求共享。
- 必须与
@InitBinder的用途- 自定义数据绑定,例如注册日期格式、禁止绑定某些字段(
setDisallowedFields)。 - 在每个控制器方法执行前调用,仅对当前控制器生效。全局化可通过
@ControllerAdvice中的@InitBinder实现。
- 自定义数据绑定,例如注册日期格式、禁止绑定某些字段(
@ControllerAdvice的高级用法- 可指定
annotations、basePackages、assignableTypes等属性限定增强的范围。 - 除了异常处理,还可包含
@ModelAttribute(全局模型数据)、@InitBinder(全局数据绑定)方法。
- 可指定
- RESTful与注解组合
Spring推荐使用@RestController和映射注解组合,如:@RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") public User get(@PathVariable Long id) { ... } @PostMapping @ResponseStatus(HttpStatus.CREATED) public User post(@RequestBody User user) { ... } } -
跨域注解
@CrossOrigin
可配置origins(允许的域)、methods(允许的方法)、allowedHeaders、allowCredentials等,用于解决CORS问题。 - Spring Boot中的自动配置
Spring Boot通过spring-boot-starter-web自动注册了RequestMappingHandlerMapping、RequestMappingHandlerAdapter等,无需额外配置即可使用这些注解。

【问题】 Spring MVC中函数的返回值是什么?怎么样把ModelMap里面的数据放入Session里面?
【参考答案】 一、Spring MVC中控制器方法的返回值类型 Spring MVC支持多种返回值类型,以适应不同的业务场景。常见的有:
String类型- 返回逻辑视图名,由
ViewResolver解析为实际视图。 - 可以添加
redirect:或forward:前缀实现重定向或转发,例如"redirect:/user/list"。 - 配合
Model参数传递数据到视图。
- 返回逻辑视图名,由
ModelAndView类型- 封装了视图名和模型数据,可手动添加模型属性,并指定视图。
- 适用于需要同时设置视图和数据且不想使用Model参数的情况。
void类型- 方法可通过
HttpServletResponse直接输出响应内容(如response.getWriter().write()),或配合@ResponseStatus设置状态码。 - 如果方法返回
void且没有写入响应,通常视图解析器会根据请求URL推断视图名(默认的RequestToViewNameTranslator)。
- 方法可通过
Model、Map、ModelMap类型- 这些类型作为方法参数时,可以在方法体内添加数据,它们会暴露给视图,此时方法返回值通常为
String逻辑视图名。
- 这些类型作为方法参数时,可以在方法体内添加数据,它们会暴露给视图,此时方法返回值通常为
HttpEntity/ResponseEntity类型- 可完全控制HTTP响应(包括状态码、头信息、响应体)。例如返回
ResponseEntity<User>。
- 可完全控制HTTP响应(包括状态码、头信息、响应体)。例如返回
- 标注了
@ResponseBody的方法- 返回值直接作为HTTP响应体,通过
HttpMessageConverter转换为JSON/XML等格式。支持任意Java类型(如User、List、Map等)。
- 返回值直接作为HTTP响应体,通过
Callable、DeferredResult、WebAsyncTask等异步类型- 用于异步请求处理,提升服务器吞吐量。
- 其他类型
View对象:直接返回View实现。ModelAndViewResolver等自定义类型。
二、如何将ModelMap中的数据放入Session
Spring MVC提供了@SessionAttributes注解,用于将模型(Model)中的指定属性自动存储到HTTP Session中,从而实现跨请求的数据共享。具体步骤:
- 在控制器类上标注
@SessionAttributes@Controller @SessionAttributes("user") // 指定需要存入Session的模型属性名 public class UserController { // ... }可以指定多个属性名:
@SessionAttributes({"user", "address"})。 - 在处理方法中添加同名模型属性
通过Model、ModelMap或@ModelAttribute向模型中添加与@SessionAttributes同名的属性时,该属性会自动同步到Session中。@GetMapping("/login") public String login(User user, Model model) { model.addAttribute("user", user); // 该属性会被自动存入Session return "home"; } - 在后续请求中从Session获取数据
可以使用@SessionAttribute注解从Session中获取属性,或通过HttpSession手动获取。@GetMapping("/profile") public String profile(@SessionAttribute("user") User user) { // 使用Session中的user对象 return "profile"; } - 清除Session中的属性
如果需要手动移除Session中的属性,可以使用SessionStatus的setComplete()方法,或在HttpSession上调用removeAttribute。@GetMapping("/logout") public String logout(SessionStatus status) { status.setComplete(); // 清除当前控制器管理的所有@SessionAttributes属性 return "redirect:/login"; }
注意:@SessionAttributes通常用于在多个请求之间保持临时数据(如表单步骤、向导模式),而不是替代业务层面的Session管理。另外,它会将数据同时存储在Model和Session中,需注意线程安全和数据一致性。
【大白话解释于举例说明】
- 返回值类型比喻:
控制器方法就像厨师做菜,返回值告诉服务员(Spring MVC)如何把菜端给客人。- 返回
String:厨师说“按菜单名‘鱼香肉丝’去装盘”,服务员根据菜单名找盘子。 - 返回
ModelAndView:厨师直接把菜和盘子一起交给服务员。 - 返回
void:厨师自己直接把菜端给客人(通过HttpServletResponse)。 - 标注
@ResponseBody:厨师把菜直接打包成外卖盒(JSON)递给客人,不用盘子。
- 返回
- ModelMap放入Session:
想象你在餐厅点了一份套餐,但套餐分量太大一次吃不完,你想把剩下的寄存在餐厅(Session)下次来再吃。@SessionAttributes就像你告诉服务员:“这道菜(属性名)我还没吃完,帮我存起来。”之后每次来,服务员都会自动帮你把存的菜端出来。
示例代码:
@Controller
@SessionAttributes("cart") // 告诉Spring,购物车数据要存Session
public class CartController {
@GetMapping("/add")
public String addItem(Item item, Model model) {
List<Item> cart = (List<Item>) model.getAttribute("cart");
if (cart == null) {
cart = new ArrayList<>();
}
cart.add(item);
model.addAttribute("cart", cart); // 存入Model,同时自动同步到Session
return "cartView";
}
@GetMapping("/checkout")
public String checkout(@SessionAttribute("cart") List<Item> cart) { // 从Session获取
// 处理结算
return "checkout";
}
@GetMapping("/clear")
public String clear(SessionStatus status) {
status.setComplete(); // 清空Session中的cart
return "redirect:/cart";
}
}
【扩展知识点详解】
-
@SessionAttributes的原理
Spring MVC通过SessionAttributesHandler管理标注的属性。当请求处理方法向Model中添加同名属性时,Model的addAttribute操作会被拦截,同时调用HttpSession.setAttribute()。这些属性在Model和Session中保持同步,直到调用SessionStatus.setComplete()清除。 @SessionAttributes的注意事项- 只对当前控制器有效,不会影响其他控制器。
- 可能导致数据在多个请求间意外共享,需注意并发修改。
- 通常用于短生命周期的会话数据(如表单填写步骤),而不是长期用户会话(如登录用户信息应通过
HttpSession直接管理)。 - 使用
setComplete()只会清除当前控制器管理的@SessionAttributes属性,不会清除Session中其他属性。
- 其他从Session获取数据的方式
- 使用
@SessionAttribute注解在方法参数上获取特定属性(与@RequestParam类似)。 - 直接注入
HttpSession对象,调用getAttribute()。 - 在JSP中通过
${sessionScope.attributeName}访问。
- 使用
- 返回值类型的详细说明
- String:配合
Model参数使用,是最常见的方式。 - ModelAndView:适合在旧式控制器或需要同时设置视图和数据时使用。
- void:通常用于直接输出响应(如下载文件)或异步处理。
- ResponseEntity:可精细控制响应,如返回
ResponseEntity.ok().header("Custom", "value").body(data)。 @ResponseBody:常用于REST API,返回JSON/XML,可结合HttpEntity作为参数获取请求体。- 异步返回值:
Callable在独立线程中执行,DeferredResult允许从其他线程异步设置结果。
- String:配合
-
视图解析器与返回值
返回String或ModelAndView时,视图解析器根据配置(如InternalResourceViewResolver)将逻辑视图名转换为物理视图路径。若返回void且没有显式写入响应,默认视图名由RequestToViewNameTranslator根据请求URL生成。 - @ModelAttribute的变体
- 作为方法参数:将请求参数绑定到对象,并自动加入
Model。 - 作为方法级注解:在控制器每个请求方法前执行,返回值放入
Model,常用于准备公共数据。
如果方法级@ModelAttribute返回的对象与@SessionAttributes同名,也会存入Session。
- 作为方法参数:将请求参数绑定到对象,并自动加入
-
Session属性的清除时机
除了手动调用SessionStatus.setComplete(),还可以通过HttpSession.invalidate()使整个会话失效,但需注意影响范围。 - 与Spring Session的集成
Spring Session提供了集群环境下Session管理的解决方案,可透明地将Session存储到Redis等外部存储,与@SessionAttributes不冲突,但需注意序列化问题。
【问题】 Spring MVC中过滤器和拦截器的区别是什么?联系是什么?什么时候用过滤器什么时候用拦截器?执行顺序是怎样的?
【参考答案】 一、过滤器(Filter)和拦截器(Interceptor)的概念
- 过滤器:是Java Servlet规范中的一部分,基于函数回调(doFilter方法),可以对请求和响应进行预处理和后处理。过滤器可以拦截任何Web资源(包括Servlet、JSP、静态资源等),在请求到达Servlet之前和响应离开Servlet之后执行。
- 拦截器:是Spring MVC框架提供的组件,基于Java反射和AOP机制,通过实现
HandlerInterceptor接口来拦截对处理器(Controller方法)的调用。拦截器仅在请求进入DispatcherServlet后、到达Controller之前,以及Controller执行后、视图渲染前等时机执行,可以获取处理器方法的信息。
二、两者的联系
- 两者都实现了对请求的拦截,可以用于实现横切关注点(如日志、权限检查、性能监控等),且都支持链式调用。
- 在Spring MVC应用中,过滤器先于拦截器执行,共同完成请求的处理。
三、两者的主要区别
| 区别点 | 过滤器(Filter) | 拦截器(Interceptor) |
|——–|——————|————————|
| 规范归属 | Servlet规范,与Spring无关 | Spring框架规范,依赖Spring容器 |
| 作用范围 | 可拦截所有Web资源(包括静态资源、JSP、Servlet等) | 仅拦截Spring MVC的控制器请求(即经过DispatcherServlet的请求) |
| 细粒度 | 基于请求和响应对象,无法获取具体处理器方法的信息 | 可以获取处理器方法(HandlerMethod)的信息,如方法名、参数等 |
| 执行时机 | 在请求进入Servlet容器后、进入Servlet之前执行,以及在Servlet处理后、响应返回前执行 | 在请求进入DispatcherServlet后、进入Controller之前,Controller之后、视图渲染之前,以及整个请求完成后执行 |
| 依赖容器 | 依赖于Servlet容器,无法直接注入Spring Bean(但可通过特殊方式获取) | 依赖于Spring容器,可以方便地注入Spring Bean |
| 配置方式 | 在web.xml中配置或通过@WebFilter注解(需Servlet 3.0+) | 实现HandlerInterceptor接口,并在Spring配置中注册(如WebMvcConfigurer) |
| 使用场景 | 适用于全局性的、与业务无关的操作,如字符编码设置、跨域处理、请求日志记录、XSS/SQL注入防护等 | 适用于与业务相关的、需要获取处理器方法信息的操作,如权限验证、性能监控、日志记录(记录具体方法)、多语言处理等 |
四、使用场景选择
- 使用过滤器:当你需要处理所有请求(包括静态资源),或者操作与Spring无关时,例如设置请求编码、压缩响应内容、添加通用响应头、防止缓存等。
- 使用拦截器:当你需要针对Spring MVC的控制器方法进行精细化处理,例如判断用户是否登录(需获取请求的URL对应的方法权限)、记录方法执行时间、在方法执行前后添加公共数据等。
五、执行顺序
- 过滤器链执行(按配置顺序):
- 请求到达时,依次执行所有过滤器的
doFilter方法中chain.doFilter之前的代码(前置处理)。 - 调用
chain.doFilter将请求传递给下一个过滤器或目标资源(Servlet)。
- 请求到达时,依次执行所有过滤器的
- 拦截器链执行(按注册顺序):
- 进入
DispatcherServlet后,按注册顺序执行所有拦截器的preHandle方法。 - 若某个
preHandle返回false,则中断请求,后续拦截器及控制器不再执行,但已执行的拦截器的afterCompletion方法会逆序调用。 - 控制器方法执行后,按注册逆序执行拦截器的
postHandle方法(在视图渲染前)。 - 视图渲染后,按注册逆序执行拦截器的
afterCompletion方法(在整个请求完成之后,可用于资源清理)。
- 进入
- 过滤器后置处理:
- 请求处理完成后(包括视图渲染),响应会沿着过滤器链反向传递,执行
doFilter方法中chain.doFilter之后的代码(后置处理)。
- 请求处理完成后(包括视图渲染),响应会沿着过滤器链反向传递,执行
总结:请求执行顺序为:过滤器前置 → 拦截器前置 → 控制器 → 拦截器后置 → 视图渲染 → 拦截器完成 → 过滤器后置。
【大白话解释于举例说明】
- 过滤器好比小区大门:无论你是业主(动态请求)还是外卖员(静态资源),进小区都要先经过大门。大门可以检查健康码(编码设置)、登记信息(日志),然后放行。出小区时也可能检查。
- 拦截器好比单元门禁:只有业主(经过DispatcherServlet的请求)才会走到单元门口。门禁可以识别你是谁(权限验证),记录你进入和离开的时间(性能监控),甚至在你进门前提醒你带钥匙(前置处理),出门后关门(后置处理)。
例子:假设一个网站需要记录所有请求的日志(包括图片等静态资源),同时需要对每个控制器方法进行权限检查。
- 过滤器可以用来记录请求URL、IP、耗时(因为所有请求都会经过)。
- 拦截器可以用来检查用户是否有权限访问某个具体方法,因为拦截器能知道请求的是哪个方法,以及方法的注解信息。
【扩展知识点详解】
- 过滤器的核心接口与方法
- 实现
javax.servlet.Filter接口,重写init()、doFilter()、destroy()方法。 - 在
doFilter中通过FilterChain调用下一个过滤器或目标资源。
- 实现
- 拦截器的核心接口与方法
- 实现
org.springframework.web.servlet.HandlerInterceptor接口,重写preHandle()、postHandle()、afterCompletion()方法。 preHandle返回true则继续执行,返回false则中断请求。
- 实现
-
拦截器的注册
通过实现WebMvcConfigurer接口并重写addInterceptors方法,添加自定义拦截器实例,并可指定拦截路径(addPathPatterns)和排除路径(excludePathPatterns)。 -
过滤器与拦截器的混合使用
在Spring Boot应用中,可以通过@WebFilter和@ServletComponentScan注册过滤器,也可以通过FilterRegistrationBean注册。拦截器则通过@Configuration类配置。 -
过滤器获取Spring Bean
过滤器本身不受Spring管理,但可以通过@Autowired注入(需将过滤器声明为Spring Bean,并使用DelegatingFilterProxy代理)或从WebApplicationContext中手动获取。 - 拦截器的执行细节
postHandle在视图渲染前执行,可以修改ModelAndView。afterCompletion在视图渲染后执行,可以用于清理资源、记录最终异常等。
-
性能考虑
过滤器在Servlet容器层面执行,适合全局性操作;拦截器在Spring MVC内部执行,适合业务相关操作。如果业务不需要Spring上下文,优先使用过滤器以减少依赖。 - 常见问题
- 过滤器中对请求体的读取可能导致后续无法再次读取(因为流只能读一次),可使用
ContentCachingRequestWrapper包装。 - 拦截器中可以获取处理器方法上的注解,用于权限控制,例如检查方法是否有
@RequiresPermission注解。
- 过滤器中对请求体的读取可能导致后续无法再次读取(因为流只能读一次),可使用
- 执行顺序图示
客户端请求 ↓ Filter1.doFilter() 前置 ↓ Filter2.doFilter() 前置 ↓ DispatcherServlet ↓ Interceptor1.preHandle() ↓ Interceptor2.preHandle() ↓ Controller方法 ↓ Interceptor2.postHandle() ↓ Interceptor1.postHandle() ↓ 视图渲染 ↓ Interceptor2.afterCompletion() ↓ Interceptor1.afterCompletion() ↓ Filter2.doFilter() 后置 ↓ Filter1.doFilter() 后置 ↓ 客户端响应
springBoot
启动流程
【问题】 什么是Spring Boot?和Spring有什么关系?优点是什么?启动流程具体是怎样的?什么是starter?怎么自定义一个starter?什么是autoconfiguation?如何在Spring Boot应用程序中使用Jetty而不是Tomcat?
【参考答案】 一、什么是Spring Boot?和Spring有什么关系? Spring Boot 是Spring家族中的一个子项目,旨在简化Spring应用程序的创建和开发过程。它基于“约定优于配置”的原则,提供了自动配置、起步依赖、嵌入式Web服务器等特性,使开发者能够快速构建独立、生产级的Spring应用。
与Spring的关系:
- Spring Boot 建立在Spring Framework之上,是对Spring框架的进一步封装和增强,而不是替代品。
- Spring Framework提供了IoC、AOP、事务管理等核心功能,而Spring Boot则通过自动配置和起步依赖,降低了使用Spring的复杂性,让开发者无需手动配置大量的XML或Java配置。
- 使用Spring Boot可以更方便地使用Spring生态中的其他项目(如Spring Data、Spring Security等)。
二、优点是什么?
- 快速构建:通过
spring-boot-starter起步依赖,可以快速引入所需功能,简化Maven/Gradle配置。 - 自动配置:Spring Boot根据类路径中的依赖自动配置Spring应用,减少手动配置工作。
- 嵌入式Web服务器:内置Tomcat、Jetty或Undertow,无需部署WAR包,可直接运行JAR包启动应用。
- 生产就绪特性:提供健康检查、指标监控、外部化配置等(通过Spring Actuator),便于运维和监控。
- 无代码生成和XML配置:完全基于注解和Java配置,无需编写繁琐的XML。
- 与Spring生态无缝集成:轻松整合Spring Data、Spring Security、Spring Cloud等。
- 易于测试:提供
@SpringBootTest等注解,简化集成测试。
三、启动流程具体是怎样的?
Spring Boot应用的启动入口通常是包含main方法的类,并使用@SpringBootApplication注解。启动流程分为两个阶段:构造SpringApplication实例和执行run方法。
- 构造
SpringApplication实例 当调用SpringApplication.run(主类.class, args)时,首先会创建SpringApplication对象,初始化工作包括:- 将传入的
sources参数(通常是主类)保存到SpringApplication属性中。 - 根据类路径推断当前应用类型(是否为Web应用,通过是否存在
javax.servlet.Servlet和Spring的WebApplicationContext类判断),并设置webApplicationType。 - 从
META-INF/spring.factories中加载所有ApplicationContextInitializer实现类,并设置到initializers属性中。 - 从
META-INF/spring.factories中加载所有ApplicationListener实现类,并设置到listeners属性中。 - 通过堆栈信息推断并设置主类(
mainApplicationClass)。
- 将传入的
- 执行
run方法 构造完成后调用run方法,步骤如下: - 启动计时器:创建
StopWatch实例并开始计时,用于记录启动耗时。 - 初始化监听器:从
spring.factories加载SpringApplicationRunListener(默认只有一个EventPublishingRunListener),并遍历调用它们的starting()方法,通知监听器应用正在启动。 - 创建应用参数对象:封装命令行参数
args为ApplicationArguments实例。 - 准备环境:
- 根据应用类型创建对应的
ConfigurableEnvironment(如StandardServletEnvironment)。 - 将命令行参数和系统属性等添加到环境中。
- 调用
SpringApplicationRunListener的environmentPrepared()方法,发布ApplicationEnvironmentPreparedEvent事件,允许监听器对环境进行修改(如添加配置文件中的属性)。 - 将环境与SpringApplication绑定。
- 根据应用类型创建对应的
- 打印Banner:根据配置在控制台打印Banner(可自定义或关闭)。
- 创建Spring容器:
- 根据应用类型创建对应的
ApplicationContext(如AnnotationConfigServletWebServerApplicationContext)。 - 将准备好的环境设置到容器中。
- 调用
ApplicationContextInitializer对容器进行初始化(此时会发布ApplicationContextInitializedEvent事件)。 - 将
ApplicationArguments和Banner等作为Bean注册到容器。
- 根据应用类型创建对应的
- 刷新容器:调用
AbstractApplicationContext.refresh()方法,执行Spring容器的核心刷新流程,包括Bean的扫描、创建、自动配置等。在此过程中,内嵌Web服务器(如Tomcat)会启动。 - 刷新后处理:刷新完成后,调用
SpringApplicationRunListener的started()方法,发布ApplicationStartedEvent事件。 - 调用Runner:获取容器中所有
ApplicationRunner和CommandLineRunner类型的Bean,并执行它们的回调方法(用于在启动后执行自定义逻辑)。 - 发布就绪事件:调用
SpringApplicationRunListener的ready()方法,发布ApplicationReadyEvent事件,表示应用已完全启动,可以处理请求。 - 返回容器:
run方法返回ConfigurableApplicationContext实例,通常由应用持有。
注:若启动过程中发生异常,会调用监听器的failed()方法,并抛出异常。
四、什么是starter? Starter 是Spring Boot中的一种特殊依赖描述符,它聚合了一组相关的依赖库,并提供了自动配置的支持。开发者只需引入一个starter,即可获得该功能所需的所有依赖和默认配置,无需手动添加各个库的依赖。
例如,spring-boot-starter-web包含了Spring MVC、内嵌Tomcat、Jackson等依赖,并自动配置了DispatcherServlet、视图解析器等组件。
五、怎么自定义一个starter? 自定义starter的步骤:
- 创建两个模块(通常):
- autoconfigure模块:包含自动配置代码。
- starter模块:空项目,仅依赖autoconfigure模块和其他必要依赖。 也可以合并为一个模块,但官方推荐分离。
- 编写自动配置类:
- 使用
@Configuration和@Conditional系列注解(如@ConditionalOnClass、@ConditionalOnProperty)定义在什么条件下创建哪些Bean。 - 在
META-INF/spring.factories文件中注册自动配置类:org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.MyAutoConfiguration。
- 使用
- 提供配置属性(可选):
- 创建
@ConfigurationProperties类,用于绑定配置文件中的属性。 - 在自动配置类中通过
@EnableConfigurationProperties启用。
- 创建
- 编写starter模块的pom:
- 引入autoconfigure模块和所需依赖。
- 通常无需代码,仅作为依赖聚合。
- 安装到Maven仓库,供其他项目使用。
六、什么是autoconfiguration? AutoConfiguration(自动配置)是Spring Boot的核心机制,它根据类路径中的依赖、定义的Bean以及配置文件,自动配置Spring应用所需的组件。其实现原理:
- 使用
@EnableAutoConfiguration注解导入AutoConfigurationImportSelector。 AutoConfigurationImportSelector通过SpringFactoriesLoader加载META-INF/spring.factories中配置的自动配置类。- 自动配置类通常带有
@Conditional条件注解,确保在满足条件时才生效(如某个类存在、某个属性被设置等)。 - 自动配置类会创建一系列默认的Bean,同时允许用户通过自定义Bean覆盖。
七、如何在Spring Boot应用程序中使用Jetty而不是Tomcat? Spring Boot默认使用Tomcat作为嵌入式Servlet容器。要替换为Jetty,只需排除Tomcat依赖并引入Jetty starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
如果是Gradle:
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
implementation 'org.springframework.boot:spring-boot-starter-jetty'
Spring Boot会自动检测Jetty的依赖并配置JettyServletWebServerFactory。
【大白话解释于举例说明】
- Spring Boot是什么:好比你要建一栋房子,Spring Framework提供了砖瓦、水泥(基础功能),但你需要自己设计图纸、搬运材料。而Spring Boot就像一个精装房开发商,不仅提供了建筑材料,还帮你设计好了户型、装修好了(自动配置),你只需拎包入住(写业务代码)。
- 优点:快速、省事、自带家具(嵌入式服务器)、还配有物业(Actuator)。
- 启动流程:就像楼盘开盘,先确定楼盘类型(普通住宅还是别墅),然后通知物业(监听器),接着平整土地(准备环境),搭建框架(创建上下文),装修(刷新上下文),最后交付使用(运行Runner)。
- starter:一个“装修套餐”,比如“北欧风套餐”包含了地板、灯具、家具,你只需选这个套餐,所有东西自动配齐。
- 自定义starter:你自己设计一个“智能家居套餐”,把需要的设备打包,并写明在什么条件下安装(自动配置),然后发布到市场(Maven仓库),别人引入就能用。
- autoconfiguration:自动判断你的房子里有没有智能灯泡,如果有就自动安装智能开关。
- 替换Jetty:原来房子默认配了Tomcat牌门锁,你不喜欢,就把它拆了(排除依赖),换成Jetty牌门锁(引入Jetty starter),房子会自动识别并装上。
【扩展知识点详解】
- @SpringBootApplication注解:组合了
@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。 - SpringApplication的run方法:核心入口,内部会创建
SpringApplication实例,调用run方法。 - spring.factories文件:位于
META-INF下,用于注册自动配置类、监听器、初始化器等,是Spring Boot SPI机制的关键。 - 条件注解:如
@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等,是自动配置的基石。 - 嵌入式Web容器的切换:除了Jetty,还有Undertow,可通过类似方式切换。同时可配置SSL、端口等。
- 自定义starter的命名规范:通常命名为
xxx-spring-boot-starter,官方starter以spring-boot-starter-xxx命名。 - 自动配置的优先级:用户自定义Bean优先于自动配置,可以通过
@Primary或@ConditionalOnMissingBean实现覆盖。 - 启动流程中的关键事件:
ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent、ApplicationPreparedEvent、ApplicationStartedEvent、ApplicationReadyEvent等,可用于扩展。 - ApplicationRunner和CommandLineRunner:用于在启动后执行特定代码,区别在于参数类型不同。
- 外部化配置:Spring Boot支持从
application.properties/yml、环境变量、命令行参数等多种来源加载配置,优先级明确。

【问题】 请详细讲一下@SpringBootApplication和@EnableAutoConfiguration两个注解。
【参考答案】
一、@SpringBootApplication 注解
@SpringBootApplication 是 Spring Boot 中最核心的注解,通常标注在启动类上。它是一个复合注解,组合了以下三个注解的功能:
- @SpringBootConfiguration:本质上是
@Configuration,表明该类是一个配置类,可以声明 Bean 或导入其他配置。 - @EnableAutoConfiguration:启用 Spring Boot 的自动配置机制,尝试根据类路径依赖自动配置 Spring 应用。
- @ComponentScan:启用组件扫描,默认扫描启动类所在包及其子包下的所有带有
@Component及其派生注解(如@Service、@Repository、@Controller)的类,并将其注册为 Bean。
等价关系:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
等同于:
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
作用:通过一个注解简化了 Spring Boot 应用的配置,无需手动编写三个注解。
二、@EnableAutoConfiguration 注解
@EnableAutoConfiguration 是 Spring Boot 自动配置功能的核心开关。它告诉 Spring Boot 基于添加的 jar 依赖和配置,自动配置 Spring 应用程序所需的 Bean。
注解定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
关键点:
@AutoConfigurationPackage:将主配置类(即标注了@EnableAutoConfiguration的类)所在的包注册为“自动配置包”,以便后续自动配置类可以扫描该包下的组件(比如 JPA 实体)。@Import(AutoConfigurationImportSelector.class):导入AutoConfigurationImportSelector,这是自动配置的核心实现类,负责从META-INF/spring.factories中加载所有候选的自动配置类,并根据条件(@Conditional)决定哪些配置生效。
工作原理
-
加载候选自动配置类:
AutoConfigurationImportSelector实现了ImportSelector接口,其selectImports方法会扫描所有META-INF/spring.factories文件中 key 为org.springframework.boot.autoconfigure.EnableAutoConfiguration的 value,这些 value 是自动配置类的全限定名列表。Spring Boot 自动配置模块(如spring-boot-autoconfigure)提供了大量预定义的自动配置类,例如WebMvcAutoConfiguration、DataSourceAutoConfiguration等。 -
过滤与排序:
加载后,会应用一系列的过滤机制,如排除指定的自动配置类(通过exclude属性)、根据条件注解进行判断。排序确保配置类的顺序符合要求(通过@AutoConfigureOrder、@AutoConfigureBefore、@AutoConfigureAfter)。 -
条件化配置:
每个自动配置类通常都带有@Conditional注解(如@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnProperty等),只有在满足特定条件时才会生效。例如,DataSourceAutoConfiguration只有在类路径中存在DataSource类且用户没有自定义DataSourceBean 时才会自动配置数据源。 -
注册 Bean:
最终,符合条件的自动配置类会被导入为普通的@Configuration类,其中的@Bean方法会被执行,将所需的 Bean 注册到容器中。
三、两者的关系
@SpringBootApplication 包含 @EnableAutoConfiguration,所以标注了 @SpringBootApplication 的启动类自动开启了自动配置功能。此外,@SpringBootApplication 还包含了组件扫描和配置类功能,三者共同构成了 Spring Boot 应用的基石。
四、常用配置与扩展
- 排除特定自动配置:
如果不想启用某个自动配置,可以在@SpringBootApplication或@EnableAutoConfiguration中使用exclude属性:@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class Application { ... }或者在配置文件中通过
spring.autoconfigure.exclude属性指定。 -
自定义自动配置:
可以编写自己的自动配置类,并注册到META-INF/spring.factories中,实现自定义的自动配置。 - 条件注解:
自动配置的强大之处在于条件注解,了解常用的条件注解有助于理解自动配置的生效时机:@ConditionalOnClass:当类路径中存在指定类时。@ConditionalOnMissingClass:当类路径中不存在指定类时。@ConditionalOnBean:当容器中存在指定 Bean 时。@ConditionalOnMissingBean:当容器中不存在指定 Bean 时(常用于覆盖默认配置)。@ConditionalOnProperty:当配置文件中有指定属性且符合要求时。@ConditionalOnResource:当类路径中存在指定资源文件时。@ConditionalOnWebApplication:当应用是 Web 应用时。@ConditionalOnNotWebApplication:当应用不是 Web 应用时。
【大白话解释】
- @SpringBootApplication:就像是一个“全家桶”套餐,包含了主食(配置类)、配菜(自动配置)和餐具(组件扫描)。你只需要点这个套餐,就能直接开吃。
- @EnableAutoConfiguration:好比一个“智能家居总开关”,打开它后,家里会根据你添置的家电(依赖)自动开启相应的功能。比如你买了智能灯泡(引入
spring-boot-starter-data-redis),它就会自动帮你配好 Redis 的连接;你买了智能音箱(引入spring-boot-starter-web),它就自动配好 Web 服务器。
【扩展知识点】
- spring.factories 文件的位置:位于每个 jar 包的
META-INF目录下,Spring Boot 通过SpringFactoriesLoader加载。 - 自动配置类的命名规范:通常以
AutoConfiguration结尾,例如DataSourceAutoConfiguration。 - 自动配置的覆盖原则:如果用户自定义了同类型的 Bean,通常自动配置的 Bean 不会生效(通过
@ConditionalOnMissingBean保证),因此用户可以轻松覆盖默认配置。 - @AutoConfigurationPackage 的作用:它注册了一个
AutoConfigurationPackages的 Bean,用于保存基础包路径,供其他自动配置类(如 JPA 实体扫描)使用。 - 调试自动配置:可以在配置文件中设置
debug=true或启用--debug,启动时控制台会输出自动配置报告,显示哪些自动配置类生效(positive matches)和哪些未生效(negative matches)。