Spring = 帮你创建对象 + 管理对象 + 给对象注入依赖 + 给对象加功能 + 管理事务 + 处理 Web 请求
作用域,就是:
这个对象要创建几个?什么时候销毁?
最常见的有 6 种:
| 作用域 | 傻子理解 |
|---|---|
| singleton | 整个 Spring 容器就一个 |
| prototype | 每次要都给你新建一个 |
| request | 一个 HTTP 请求一个 |
| session | 一个用户会话一个 |
| application | 整个 Web 应用一个 |
| websocket | 一个 WebSocket 会话一个 |
最重要的是:
singleton 和 prototype。
比如:
@Service
public class UserService {
}
默认就是 singleton。
意思是整个 Spring 容器里面通常只有一个 UserService。
Spring Bean 主要有 singleton、prototype、request、session、application、websocket 六种作用域。默认是 singleton,也就是 Spring 容器中只创建一个实例。prototype 则是在每次获取 Bean 时创建新的实例,其余作用域主要应用于 Web 环境。
@Qualifier 注解有什么作用?假设 Spring 里面有两个:
@Service
class DogService implements AnimalService {}
@Service
class CatService implements AnimalService {}
你写:
@Autowired
private AnimalService animalService;
Spring 会懵:
“你到底要 Dog 还是 Cat?”
这时候:
@Autowired
@Qualifier("dogService")
private AnimalService animalService;
相当于告诉 Spring:
我要 Dog。
@Qualifier用于在存在多个同类型 Bean 时,进一步指定具体要注入哪一个 Bean,主要用于解决按类型注入时的歧义问题。
@Bean 和 @Component 有什么区别?它们都是:
把一个对象交给 Spring 管理。
区别主要在于:
@Component放在类上:
@Component
public class UserService {
}
告诉 Spring:
“这个类你帮我创建对象。”
@Bean放在方法上:
@Configuration
public class Config {
@Bean
public UserService userService() {
return new UserService();
}
}
告诉 Spring:
“这个方法返回的对象,你帮我放进容器。”
所以可以记:
@Component:Spring自己扫描这个类。@Bean:我通过代码告诉 Spring 创建哪个对象。
@Bean 特别适合:
第三方类不能修改源码,但你又想交给 Spring 管理。
@Component是类级别注解,Spring 通过组件扫描自动将该类注册为 Bean;@Bean是方法级别注解,通过配置类中的方法显式声明 Bean。@Bean更适合注册第三方类或需要自定义实例化逻辑的对象。
@Component、@Controller、@Repository 和 @Service 的区别?本质上:
它们都是告诉 Spring:这个类交给你管理。
只不过名字不同,代表不同职责。
@Component
├── @Controller → Controller层
├── @Service → Service层
└── @Repository → DAO/数据访问层
例如:
@Controller
class UserController
负责接收 HTTP 请求。
@Service
class UserService
负责业务逻辑。
@Repository
class UserRepository
负责数据库访问。
所以最容易记:
Controller:接请求 Service:做业务 Repository:访问数据库 Component:通用组件
这几个注解本质上都属于 Spring 的组件注册注解,最终都可以将类注册为 Bean。区别主要是语义不同,
@Controller用于控制层,@Service用于业务层,@Repository用于持久层,@Component用于通用组件。
@Primary 有什么作用?还是刚才:
DogService
CatService
两个都是 AnimalService。
如果你加:
@Primary
@Service
public class DogService implements AnimalService {
}
那么 Spring 会说:
“如果你没指定,那我默认选 Dog。”
所以:
@Qualifier= 我明确点名@Primary= 没点名时,我优先选这个
@Primary用于多个同类型 Bean 存在时指定一个默认优先注入的 Bean。如果同时使用@Qualifier,通常以@Qualifier指定的 Bean 为准。
@Value 有什么作用?就是:
把配置文件里面的值拿到 Java 代码里。
例如:
server.port=8080
然后:
@Value("${server.port}")
private int port;
最终:
port = 8080
还可以:
@Value("${name}")
private String name;
@Value用于将配置文件、环境变量或者 SpEL 表达式中的值注入到 Spring Bean 的字段、方法或构造参数中,常用于读取应用配置。
@ModelAttribute 是干什么的?这个在 Spring MVC 里面容易混。
可以理解成:
把请求里的参数,装进一个 Java 对象。
比如请求:
/user?name=Tom&age=20
Java:
@GetMapping("/user")
public String user(@ModelAttribute User user) {
System.out.println(user.getName());
System.out.println(user.getAge());
}
Spring 会帮你组装:
User
name = Tom
age = 20
另外它还可以放在方法上,把公共数据放进 Model。
所以核心记忆:
@ModelAttribute= 请求参数 → Java对象 / Model数据
@ModelAttribute主要用于 Spring MVC 中的数据绑定,可以将请求参数绑定到 JavaBean 对象,也可以在控制器方法执行前向 Model 中添加公共数据。
这是非常容易被问的题。
先看整个请求:
浏览器
↓
Filter
↓
DispatcherServlet
↓
Interceptor
↓
Controller
↓
Service
属于:
Servlet 规范
它比 Spring MVC 更早。
可以理解成:
门卫。
所有请求先过门卫。
属于:
Spring MVC
它更接近 Controller。
可以理解成:
Controller 门口的保安。
| Filter | Interceptor |
|---|---|
| Servlet 规范 | Spring MVC |
| 更早执行 | DispatcherServlet 之后 |
| 可以处理更广泛的请求 | 主要针对 Handler |
| 与 Spring MVC 解耦 | 和 Spring MVC 绑定更紧 |
Filter 属于 Servlet 规范,位于 Servlet 容器层,可以在请求进入 DispatcherServlet 之前处理请求;Interceptor 属于 Spring MVC,主要围绕 Handler 执行,可以在 Controller 执行前后进行拦截。Filter 更底层,Interceptor 更贴近 Spring MVC 的业务处理流程。
A 需要 B:
A → B
B 又需要 A:
B → A
于是:
A → B → A → B → A...
这就叫:
循环依赖。
例如:
class A {
@Autowired
private B b;
}
class B {
@Autowired
private A a;
}
循环依赖是指两个或多个 Bean 之间存在相互依赖关系,例如 A 依赖 B,同时 B 又依赖 A。Spring 对部分单例 Bean 的循环依赖可以通过三级缓存机制进行解决。
核心思想特别简单:
A 还没完全造好,就先把 A 的“半成品”放起来。
例如:
创建 A
↓
A 发现需要 B
↓
创建 B
↓
B 发现需要 A
↓
Spring:A虽然没完全创建好,但我这里有A的提前引用
↓
把这个A给B
↓
B创建完成
↓
A继续完成
这就是 Spring 解决部分循环依赖的大致思路。
但注意:
主要解决的是单例 Bean 的属性注入 / setter 注入循环依赖。
构造器循环依赖通常无法靠三级缓存解决。
例如:
A(A需要B)
B(B需要A)
因为 A 构造的时候就必须拿到 B,而 B 构造的时候又必须拿 A,两边都没机会先把“半成品”暴露出来。
Spring 主要通过三级缓存解决单例 Bean 的循环依赖。Bean 在实例化后、属性填充之前,可以提前暴露引用,使另一个 Bean 能够获取该 Bean 的提前引用,从而打破循环。需要注意的是,构造器注入产生的循环依赖通常无法通过三级缓存解决。
这个问题很容易把人问懵。
一级缓存:singletonObjects
↓
完整 Bean
二级缓存:earlySingletonObjects
↓
提前暴露的 Bean
三级缓存:singletonFactories
↓
能够产生 Bean 提前引用的工厂
关键在:
AOP 代理。
假设 A 最后不是原对象:
A原对象
↓
代理对象
如果 B 提前拿到 A,那么 Spring 希望 B 拿到的是:
代理后的A
而不是:
原始A
三级缓存里面保存的是:
ObjectFactory
它可以在需要的时候决定:
到底应该返回原对象,还是返回代理对象。
所以三级缓存的核心价值可以粗暴记成:
为了能够“提前暴露 + 必要时生成代理”。
二级缓存能够保存提前暴露的 Bean,但无法很好地解决循环依赖场景下的代理对象创建问题。三级缓存中的
ObjectFactory可以延迟获取早期 Bean 引用,并允许 Spring 在这里进行 AOP 提前代理,因此三级缓存主要是为了支持循环依赖与代理机制的结合。
面试官如果问这个,不一定真要你讲源码几万行。
你可以按照功能回答:
Spring Core
↓
Spring Beans
↓
Spring Context
↓
Spring AOP
↓
Spring TX
↓
Spring JDBC / ORM
↓
Spring Web / WebMVC
| 模块 | 干什么 |
|---|---|
| spring-core | 核心基础 |
| spring-beans | Bean管理 |
| spring-context | IOC容器、ApplicationContext |
| spring-expression | SpEL |
| spring-aop | AOP |
| spring-tx | 事务 |
| spring-jdbc | JDBC |
| spring-orm | ORM支持 |
| spring-web | Web基础 |
| spring-webmvc | Spring MVC |
| spring-test | 测试支持 |
Spring Framework 可以按功能划分为核心容器、AOP、数据访问、事务、Web 等模块。核心容器主要包括
spring-core、spring-beans、spring-context、spring-expression;AOP 主要由spring-aop提供;事务由spring-tx提供;Web 层主要包括spring-web和spring-webmvc;数据访问还包括 JDBC 和 ORM 等模块。
这是 Spring 最核心的概念之一。
以前你自己创建对象:
UserService service = new UserService();
也就是说:
对象由你创建。
IOC 后:
@Autowired
UserService service;
你不 new 了。
变成:
Spring 帮你创建对象。
所以 IOC:
控制反转。
以前:
你 → 创建对象
现在:
Spring → 创建对象
“控制”从你的代码手里:
反转到了 Spring。
IOC,即控制反转,是一种设计思想。传统方式下对象由程序主动创建,而在 Spring 中,对象的创建、组装和生命周期由 Spring 容器负责管理,从而降低对象之间的耦合。
最大的好处:
解耦。
以前:
UserService service = new UserService();
UserController 和 UserService 绑死。
现在:
@Autowired
private UserService service;
Controller 不需要关心:
UserService 到底怎么创建。
所以:
自己new
↓
对象之间关系很紧
Spring管理
↓
对象之间关系更松
IOC 的核心价值是降低对象之间的耦合,将对象创建和依赖关系管理从业务代码中抽离出来,同时也便于替换实现、测试和统一管理 Bean 生命周期。
IOC 和 DI 一定要分清。
IOC 是:
思想:对象由 Spring 管。
DI 是:
实现这种思想的一种方式:Spring 把依赖对象注入进去。
例如:
class A {
@Autowired
private B b;
}
Spring 把 B 放到 A 里面。
这就叫:
依赖注入。
一句话:
IOC 是“谁负责”,DI 是“怎么给”。
DI,即 Dependency Injection,依赖注入,是 IOC 的具体实现方式之一。Spring 根据 Bean 之间的依赖关系,将所需要的依赖对象自动注入到目标 Bean 中。
就是:
交给 Spring 管理的对象。
普通 Java 对象:
UserService service = new UserService();
如果这个对象被 Spring 容器管理:
UserService对象
↓
Spring IOC容器
↓
Spring Bean
所以:
Bean 本质还是一个 Java 对象。
Spring Bean 本质上是由 Spring IOC 容器负责实例化、配置和生命周期管理的 Java 对象。
可以把它理解成:
Bean工厂。
你跟它说:
我要 UserService
它给你:
UserService Bean
它是 Spring 最核心、最基础的 Bean 容器接口之一。
BeanFactory是 Spring IOC 容器最基础的顶层接口之一,负责 Bean 的创建、获取以及生命周期管理等核心功能。
这个名字特别容易和 BeanFactory 搞混。
BeanFactory:工厂本身就是“Bean工厂”。 FactoryBean:一个特殊的 Bean,这个 Bean 专门用来“生产另一个对象”。
例如:
class MyFactoryBean implements FactoryBean<UserService>
Spring 管理:
MyFactoryBean
↓
生产
↓
UserService
所以:
BeanFactory 是容器。 FactoryBean 是容器里的一个“工厂型 Bean”。
FactoryBean是 Spring 提供的一种特殊 Bean 接口,实现该接口后,可以通过getObject()自定义真正需要暴露到容器中的对象。它常用于复杂 Bean 的创建以及第三方组件的整合。
它可以理解成:
“别现在给我对象,等我需要的时候再给我。”
例如:
ObjectFactory<UserService>
调用:
factory.getObject();
才拿对象。
所以它的重点是:
延迟获取对象。
这也是 Spring 三级缓存里面很重要的东西。
ObjectFactory是一种延迟获取对象的工厂接口,通过getObject()在需要时获取对象。在 Spring 中可以用于延迟依赖获取,也是三级缓存处理循环依赖时的重要机制之一。
可以理解为:
升级版 BeanFactory。
它不仅能:
创建Bean
获取Bean
还提供:
事件
国际化
资源加载
环境配置
自动注册
所以:
BeanFactory
↓
基础容器
ApplicationContext
↓
功能更完整的高级容器
ApplicationContext是 Spring 提供的高级 IOC 容器接口,它在 BeanFactory 基础上增加了事件发布、国际化、资源加载、环境配置等功能,是实际 Spring 应用中最常使用的容器。
主要记 3 种:
public UserService(UserRepository repository) {
this.repository = repository;
}
@Autowired
public void setRepository(UserRepository repository) {
this.repository = repository;
}
@Autowired
private UserRepository repository;
面试里最好补一句:
实际开发更推荐构造器注入。
因为依赖关系明确,而且对象创建时依赖就是完整的,也更方便测试。
Spring 常见的依赖注入方式包括构造器注入、Setter 方法注入和字段注入。实际开发中通常更推荐构造器注入,因为依赖明确、对象创建后状态更完整,也更利于单元测试。
假设系统里有 100 个方法:
method1
method2
method3
...
method100
它们全部都要:
记录日志
检查权限
开启事务
统计耗时
你如果每个方法都写:
log();
checkPermission();
...
会非常烦。
AOP 的思想:
把这些到处重复的代码单独拿出来,需要的时候自动插进去。
例如:
执行方法
↓
先记录日志
↓
执行原方法
↓
记录执行结果
所以:
AOP = 不修改核心业务代码,就给它增加额外功能。
AOP,即面向切面编程,用于将日志、事务、权限、监控等横切关注点从核心业务逻辑中分离出来,通过动态代理等方式在目标方法执行的特定位置织入增强逻辑,从而降低代码重复和耦合。
Spring AOP 主要有:
JDK动态代理
CGLIB代理
要求:
目标类实现接口。
例如:
interface UserService {}
class UserServiceImpl implements UserService {}
那么可以代理接口。
不要求接口。
它通过:
继承目标类生成子类代理。
有接口 → JDK动态代理
没接口 → CGLIB
严格来说,Spring AOP 默认 proxyTargetClass=false 时优先使用 JDK 动态代理;如果没有合适的接口,会使用 CGLIB。
JDK:
代理接口
CGLIB:
继承目标类
CGLIB 因为要继承,所以:
final class、final method等场景会受到限制。
Spring AOP 支持 JDK 动态代理和 CGLIB 代理。默认情况下,如果目标类实现了接口,Spring 优先使用 JDK 动态代理;如果没有接口,则通常使用 CGLIB 创建子类代理。JDK 代理基于接口,CGLIB 基于继承生成代理子类。
这个问题不用一上来背源码。
假设一个方法要经过:
权限检查
↓
日志
↓
事务
↓
真正业务方法
Spring 会把这些增强逻辑组织成:
一条拦截链。
执行:
Interceptor 1
↓
Interceptor 2
↓
Interceptor 3
↓
目标方法
每一个拦截器内部通过:
proceed()
继续往下执行。
Spring AOP 的拦截链主要通过 MethodInterceptor 和 MethodInvocation 等机制实现。多个 Advisor 最终会转换成拦截器链,目标方法调用时通过
proceed()逐个执行拦截器,直到最终执行目标方法。
两者都干:
切面编程。
但方式不一样。
主要:
运行时动态代理。
也就是:
原对象
↓
代理对象
↓
执行增强
更强。
它可以:
在编译期或者加载期把代码织进去。
所以 AspectJ 能处理更广泛的连接点,而 Spring AOP 主要围绕方法执行。
Spring AOP 主要基于运行时代理实现,使用 JDK 动态代理或 CGLIB,主要针对 Spring Bean 的方法执行进行增强。AspectJ 属于更完整的 AOP 实现,可以通过编译期织入或加载期织入实现更强的切面能力,支持更多类型的连接点。
这道题建议记成:
实例化 → 属性注入 → 初始化 → 使用 → 销毁
完整一点:
BeanDefinition
↓
实例化
↓
属性注入
↓
Aware回调
↓
BeanPostProcessor前置处理
↓
初始化
↓
BeanPostProcessor后置处理
↓
得到最终Bean
↓
使用
↓
销毁
其中初始化经常包括:
@PostConstruct
InitializingBean
init-method
销毁可能包括:
@PreDestroy
DisposableBean
destroy-method
就是:
出生 → 穿衣服 → 做初始化 → 上班 → 下班销毁
Spring Bean 生命周期主要包括 Bean 实例化、属性依赖注入、Aware 回调、BeanPostProcessor 前后置处理、初始化、使用以及销毁等阶段。最终返回给业务代码的 Bean 可能已经经过后置处理器增强,例如生成 AOP 代理。
Spring MVC 是:
专门处理 HTTP 请求的。
比如浏览器:
GET /user/1
Spring MVC 负责:
收到请求
↓
找到哪个Controller
↓
调用哪个方法
↓
把参数传进去
↓
拿结果
↓
返回给浏览器
Spring MVC 是基于 MVC 设计模式的 Web 框架,核心职责是处理 HTTP 请求,将请求映射到对应的 Controller 方法,完成参数绑定、业务调用以及响应结果处理。
这道题直接记流程:
浏览器
↓
DispatcherServlet
↓
HandlerMapping
↓
找到 Controller 方法
↓
HandlerAdapter
↓
执行 Controller
↓
返回 ModelAndView / ResponseBody
↓
ViewResolver(视图场景)
↓
返回响应
总指挥。
找人:
“这个 URL 应该找哪个 Controller?”
帮总指挥真正执行 Controller。
干业务。
找页面。
不过现在 Spring Boot 的 REST API 大量使用:
@RestController
直接返回 JSON,所以很多时候不会走传统 JSP 视图解析。
Spring MVC 以
DispatcherServlet为核心。请求首先进入DispatcherServlet,然后通过HandlerMapping找到对应的 Handler,再由HandlerAdapter执行 Controller 方法,完成参数解析和业务调用,最后根据返回结果进行消息转换或视图解析,形成 HTTP 响应。
可以理解成:
父容器
Spring根容器
│
└──── 子容器
Spring MVC容器
一般:
放:
Service
Repository
DataSource
放:
Controller
HandlerMapping
HandlerAdapter
关键规则:
子容器可以访问父容器,父容器不能访问子容器。
就像:
父公司
↑
知道子公司
子公司
↑
可以使用总公司的资源
在传统 Spring MVC 架构中,通常存在 Root WebApplicationContext 和 WebApplicationContext 的父子关系。父容器主要管理 Service、Repository 等业务 Bean,子容器主要管理 Controller 和 MVC 相关组件。子容器可以访问父容器中的 Bean,但父容器不能直接访问子容器中的 Bean。
这一题不要背 20 个。
重点说这几个:
BeanFactory
创建 Bean。
默认 Bean:
singleton
Spring AOP:
原对象
↓
代理对象
例如:
JdbcTemplate
把固定流程写好,你只需要填具体逻辑。
Spring 事件:
ApplicationEvent
ApplicationListener
不同策略实现不同算法。
Spring MVC / AOP 的拦截链都可以体现这种思想。
Spring 中大量使用设计模式,例如 BeanFactory 体现工厂模式,默认单例 Bean 体现单例模式,AOP 采用代理模式,JdbcTemplate 体现模板方法模式,ApplicationEvent 体现观察者模式,同时在 MVC 和 AOP 等模块中也可以看到策略模式和责任链思想。
这个问题要稍微注意:
Spring 的 Isolation 枚举通常包括:
DEFAULT
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE
也就是:
5 个枚举值。
但真正的标准数据库隔离级别是:
4 个。
DEFAULT 的意思:
使用数据库默认隔离级别。
读未提交
读已提交
可重复读
串行化
等级越高:
数据隔离性 ↑
并发性能 ↓
Spring 事务隔离级别在
Isolation枚举中包括DEFAULT以及四种标准数据库隔离级别:READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE。其中 DEFAULT 表示使用数据库自身的默认隔离级别。
一共:
7 种。
最重要的是前 3 个:
REQUIRED
REQUIRES_NEW
NESTED
另外:
SUPPORTS
NOT_SUPPORTED
MANDATORY
NEVER
有事务就加入,没有就创建。
不管有没有,我重新开一个。
在当前事务里面创建嵌套事务。
Spring 提供 7 种事务传播行为,包括 REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER 和 NESTED。其中 REQUIRED 是最常用的,表示当前存在事务则加入,不存在则创建新事务;REQUIRES_NEW 则会挂起当前事务并创建一个新事务。
假设:
下单
├── 创建订单
├── 扣库存
└── 记录日志
这些方法都有事务。
问题是:
到底应该一起提交,还是分开提交?
传播行为就是解决:
“别人调用我的时候,我应该跟着他的事务走,还是自己开一个?”
例如:
订单事务
↓
日志
如果日志使用:
REQUIRES_NEW
那么订单事务失败:
日志事务也可能已经独立提交。
事务传播行为用于定义一个事务方法被另一个事务方法调用时,两个方法之间应该如何处理事务边界,例如加入现有事务、创建新事务、挂起当前事务等,从而控制复杂业务场景中的事务组合方式。
不要只说“简单”。
核心可以说:
IOC
AOP
事务管理
模块化
解耦
生态完善
Spring 相当于:
把企业开发中很多麻烦的事情统一管起来。
比如:
对象创建 → Spring管
依赖关系 → Spring管
事务 → Spring管
AOP → Spring管
Web → Spring MVC
Spring 的主要优势包括 IOC 降低对象耦合、AOP 实现横切逻辑复用、统一事务管理、模块化设计以及完善的生态体系。同时它具有较好的扩展性和可测试性,因此成为 Java 企业级开发的重要基础框架。
这一题建议直接记:
Aspect
Join Point
Pointcut
Advice
Target
Proxy
Weaving
Advisor
假设:
public void buy() {}
被增强的原对象。
“我这个切面负责干什么。”
比如日志切面。
可以被增强的位置。
我要增强哪些位置。
到底增加什么代码。
Spring 创建出来的代理对象。
把增强逻辑织入目标对象执行过程。
Spring AOP 中常见概念包括 Aspect、Join Point、Pointcut、Advice、Target、Proxy 和 Weaving。其中 Pointcut 用于匹配需要增强的连接点,Advice 表示具体增强逻辑,Target 是目标对象,Proxy 是 Spring 创建的代理对象,Weaving 则表示将切面逻辑应用到目标对象的过程。
最常见的 5 种:
Before
After
AfterReturning
AfterThrowing
Around
假设:
执行 buy()
买之前干点事。
买完之后干点事。
买成功之后干点事。
买失败、抛异常之后干点事。
买之前、买之后都能控制,而且可以决定到底执不执行原方法。
所以:
Around 最强。
Spring AOP 常见通知类型包括前置通知 Before、后置通知 After、返回通知 AfterReturning、异常通知 AfterThrowing 和环绕通知 Around。环绕通知可以通过
ProceedingJoinPoint控制目标方法是否执行,并在执行前后增加逻辑。
这道题和“Spring 启动过程”有区别。
IOC 初始化可以简单理解:
读取配置
↓
扫描 Bean
↓
生成 BeanDefinition
↓
注册 BeanDefinition
↓
执行 BeanFactoryPostProcessor
↓
实例化 Bean
↓
依赖注入
↓
初始化
↓
BeanPostProcessor
↓
放入单例池
可以理解成:
Bean的说明书。
比如:
这个Bean叫什么
是什么类型
作用域是什么
依赖谁
真正创建出来的:
UserService对象
Spring IOC 容器初始化首先加载配置并完成组件扫描,将 Bean 信息解析为 BeanDefinition 并注册到容器中,然后通过 BeanFactoryPostProcessor 对 BeanDefinition 进行处理,在实例化 Bean 后完成依赖注入、初始化以及 BeanPostProcessor 前后置处理,最终将单例 Bean 放入单例缓存中供后续使用。
常见的有:
@Component 系列@Component
@Service
@Controller
@Repository
@Bean@Bean
public UserService userService() {}
@Import@Import(UserConfig.class)
老项目常见:
<bean ... />
例如:
BeanDefinitionRegistry
这种属于比较底层的方式。
Spring Bean 常见注册方式包括组件扫描注册
@Component及其派生注解、配置类中的@Bean、@Import导入、XML 配置以及通过 BeanDefinitionRegistry 等 API 进行编程式注册。
这里有一个面试坑:
“自动装配”有不同语境。
现在最常见的是:
@Autowired
@Resource
以及构造器注入。
Spring 传统 XML 时代还有:
byName
byType
constructor
所以面试最好这样回答。
本质都是:
“Spring 自动帮我找到我需要的对象。”
比如:
@Autowired
private UserService userService;
Spring:
“你要 UserService?我给你找。”
Spring 依赖注入常见方式包括
@Autowired、@Resource以及构造器注入。从传统 XML 自动装配模式来看,还存在 byName、byType、constructor 等方式。现代 Spring 开发中通常使用注解注入和构造器注入。
这题非常重要。
最经典的几个:
@Transactional
private void test()
通常不能按预期工作。
例如:
class UserService {
public void A() {
B();
}
@Transactional
public void B() {}
}
A 里面直接调用 B:
this.B()
没有经过 Spring 代理。
因此事务可能失效。
这是最常问的。
try {
...
} catch (Exception e) {
}
异常没继续抛出去。
Spring:
“你都说没异常了,那我正常提交。”
默认情况下,Spring 声明式事务主要针对:
RuntimeException 和 Error
进行回滚。
如果是受检异常,需要根据配置决定。
你自己:
new UserService();
那当然没有 Spring 代理。
例如使用不支持事务的存储引擎/操作场景。
Spring 声明式事务依赖代理机制实现,因此常见失效场景包括 Bean 没有被 Spring 管理、事务方法没有经过代理调用、同类内部方法调用、方法可见性或代理机制不满足要求、异常被捕获后没有继续抛出,以及异常类型不符合默认回滚规则等。
如果是普通 Spring:
创建 ApplicationContext
↓
读取配置
↓
扫描 Bean
↓
注册 BeanDefinition
↓
执行各种 BeanFactoryPostProcessor
↓
注册 BeanPostProcessor
↓
实例化非懒加载单例 Bean
↓
依赖注入
↓
Bean初始化
↓
容器启动完成
如果是 Spring Boot:
main()
↓
SpringApplication.run()
↓
创建 ApplicationContext
↓
准备环境
↓
加载配置
↓
自动配置
↓
扫描 / 注册 Bean
↓
刷新 IOC 容器
↓
创建 Bean
↓
启动内嵌 Web 容器
↓
应用启动完成
注意:
Spring IOC 初始化是 Spring 启动过程的一部分。
Spring 启动的核心过程是创建并刷新 ApplicationContext,包括准备环境、加载配置、注册 BeanDefinition、执行 BeanFactoryPostProcessor、注册 BeanPostProcessor、实例化非懒加载单例 Bean、完成依赖注入和初始化,最终完成容器启动。Spring Boot 则在此基础上进一步完成自动配置、内嵌 Web 容器启动等工作。
答案:
有可能。
这个题非常容易误答成:
“Spring 单例 Bean 是线程安全的。”
这是错误的。
假设:
@Service
public class UserService {
private int count = 0;
}
Spring 只创建:
一个 UserService
但可能有:
100个线程
↓
同时访问这个UserService
如果大家同时修改:
count
就有线程安全问题。
所以:
单例 ≠ 线程安全。
如果 Bean 是:
无状态的。
例如:
@Service
public class UserService {
public User getUser(Long id) {
return userMapper.selectById(id);
}
}
没有保存会变化的共享成员变量。
这种通常没问题。
Spring 单例 Bean 本身并不保证线程安全。因为单例 Bean 会被多个线程共享访问,如果 Bean 内部存在可变共享状态,就可能产生线程安全问题。实际开发中通常将 Service 等 Bean 设计为无状态对象,尽量避免保存线程相关的可变成员变量;如果确实存在共享状态,则需要通过锁、并发容器等机制保证线程安全。
你面试前真正需要建立的是下面这套关系:
Spring
│
┌─────────────┼─────────────┐
↓ ↓ ↓
IOC AOP MVC
│ │ │
│ │ └→ 处理HTTP请求
│ │
│ └→ 给方法增加额外功能
│ │
│ ├→ JDK代理
│ └→ CGLIB
│
├→ BeanFactory
├→ ApplicationContext
├→ Bean
├→ DI
├→ Bean生命周期
├→ Bean作用域
├→ Bean注册
└→ 循环依赖
│
└→ 三级缓存
Spring事务
│
┌──────────┴──────────┐
↓ ↓
隔离级别 传播行为
│ │
读未提交 REQUIRED
读已提交 REQUIRES_NEW
可重复读 NESTED
串行化 ...
你可以把整套 Spring 面试题归纳成 5 个核心问题:
1. Spring 帮我管理什么? → Bean / IOC 2. Spring 怎么把对象联系起来? → DI / 自动装配 3. Spring 怎么给对象加功能? → AOP / 动态代理 4. Spring 怎么处理 Web 请求? → Spring MVC 5. Spring 怎么管理数据库事务? → 事务 / 隔离 / 传播
然后再把:
Bean 生命周期、循环依赖、三级缓存、BeanFactory、FactoryBean、ApplicationContext
看成是 IOC 的进阶问题;
把:
Advice、Pointcut、拦截链、AspectJ
看成是 AOP 的进阶问题;
把:
事务失效、传播行为、隔离级别
看成是 事务的进阶问题。