📌 三、事务管理篇
3.1 Spring事务的传播行为有哪些?什么时候用哪种?
✅ 正确回答思路:
Spring事务的传播行为是面试高频考点,我详细说明:
一、什么是事务传播行为?
场景:
java
@Service
public class OrderService {
@Autowired
private UserService userService;
@Transactional
public void createOrder() {
// 插入订单
orderDao.insert(order);
// 调用另一个有事务的方法
userService.updateUserPoints(); // UserService的方法也有@Transactional
// 这两个事务是什么关系?
// - 在同一个事务里?
// - 各自独立的事务?
// - 还是其他关系?
}
}
事务传播行为就是定义:当一个事务方法被另一个事务方法调用时,这个方法应该如何处理事务。
二、7种传播行为
1. REQUIRED(默认)
java
@Transactional(propagation = Propagation.REQUIRED)
public void method() {
// ...
}
含义:
- 如果当前有事务,就加入这个事务
- 如果当前没有事务,就新建一个事务
示例:
java
@Service
public class OrderService {
@Autowired
private UserService userService;
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
orderDao.insert(order); // 操作1
userService.updatePoints(); // 操作2
}
}
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRED)
public void updatePoints() {
userDao.update(user); // 操作3
}
}
结果 : 操作1、2、3在同一个事务里,任何一个失败都会全部回滚。
2. REQUIRES_NEW
java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method() {
// ...
}
含义:
- 无论当前有没有事务,都新建一个事务
- 如果当前有事务,把当前事务挂起
示例:
java
@Service
public class OrderService {
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
orderDao.insert(order); // 事务A
logService.saveLog(); // 事务B(新事务)
int i = 1 / 0; // 抛异常,事务A回滚
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog() {
logDao.insert(log); // 在新事务里,不受外层事务影响
}
}
结果:
- 订单插入失败,回滚
- 但日志插入成功!因为是新事务,不受外层事务影响
使用场景: 记录操作日志,无论业务成功还是失败,日志都要保存。
3. SUPPORTS
java
@Transactional(propagation = Propagation.SUPPORTS)
public void method() {
// ...
}
含义:
- 如果当前有事务,就加入这个事务
- 如果当前没有事务,就以非事务方式执行
使用场景: 查询方法,有事务就用事务,没事务也无所谓。
4. NOT_SUPPORTED
java
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void method() {
// ...
}
含义:
- 以非事务方式执行
- 如果当前有事务,把当前事务挂起
使用场景: 不需要事务的操作,比如发送邮件、调用第三方API。
5. MANDATORY
java
@Transactional(propagation = Propagation.MANDATORY)
public void method() {
// ...
}
含义:
- 必须在一个事务中执行
- 如果当前没有事务,抛异常
使用场景: 必须在事务中执行的子方法。
6. NEVER
java
@Transactional(propagation = Propagation.NEVER)
public void method() {
// ...
}
含义:
- 必须以非事务方式执行
- 如果当前有事务,抛异常
使用场景: 极少使用。
7. NESTED(嵌套事务)
java
@Transactional(propagation = Propagation.NESTED)
public void method() {
// ...
}
含义:
- 如果当前有事务,在嵌套事务中执行
- 如果当前没有事务,等同于REQUIRED
和REQUIRES_NEW的区别:
- REQUIRES_NEW: 完全独立的新事务,互不影响
- NESTED: 嵌套事务,子事务回滚不影响父事务,但父事务回滚会影响子事务
示例:
java
@Service
public class OrderService {
@Autowired
private CouponService couponService;
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
orderDao.insert(order); // 父事务
try {
couponService.useCoupon(); // 子事务(NESTED)
} catch (Exception e) {
// 子事务失败,不影响父事务
}
// 订单还是会成功
}
}
@Service
public class CouponService {
@Transactional(propagation = Propagation.NESTED)
public void useCoupon() {
couponDao.update(coupon);
throw new RuntimeException("优惠券不可用"); // 子事务回滚
}
}
结果: 优惠券更新回滚,但订单插入成功。
底层原理: 通过**JDBC的SavePoint(保存点)**实现。
需要注意:NESTED 事务依赖底层数据库和 JDBC 驱动对 SavePoint 的支持,并非所有数据库都支持。
三、传播行为对比表
| 传播行为 | 当前有事务 | 当前无事务 | 使用场景 |
|---|---|---|---|
| REQUIRED(默认) | 加入当前事务 | 新建事务 | 大部分场景 |
| REQUIRES_NEW | 挂起当前事务,新建事务 | 新建事务 | 操作日志 |
| SUPPORTS | 加入当前事务 | 非事务执行 | 查询方法 |
| NOT_SUPPORTED | 挂起当前事务,非事务执行 | 非事务执行 | 发邮件、调API |
| MANDATORY | 加入当前事务 | 抛异常 | 必须在事务中的子方法 |
| NEVER | 抛异常 | 非事务执行 | 极少使用 |
| NESTED | 嵌套事务 | 新建事务 | 可选的子操作 |
四、实际项目经验
场景1: 订单创建流程
java
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(OrderDTO dto) {
// 1. 创建订单
Order order = new Order();
orderDao.insert(order);
// 2. 扣减库存(同一个事务)
inventoryService.deductStock(dto.getProductId(), dto.getQuantity());
// 3. 记录日志(独立事务,无论成功失败都要记录)
logService.saveOperationLog("创建订单", order.getId());
}
}
@Service
public class InventoryService {
@Transactional(propagation = Propagation.REQUIRED)
public void deductStock(Long productId, Integer quantity) {
// 扣减库存
inventoryDao.deduct(productId, quantity);
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveOperationLog(String operation, Long orderId) {
// 独立事务,不受外层事务影响
logDao.insert(new Log(operation, orderId));
}
}
场景2: 批量导入用户
java
@Service
public class UserImportService {
@Transactional(propagation = Propagation.REQUIRED)
public void importUsers(List<User> users) {
for (User user : users) {
try {
// 每个用户用嵌套事务,失败不影响其他用户
importSingleUser(user);
} catch (Exception e) {
log.error("导入用户失败: {}", user.getName(), e);
// 继续导入下一个用户
}
}
}
@Transactional(propagation = Propagation.NESTED)
public void importSingleUser(User user) {
userDao.insert(user);
// 可能抛异常
}
}
💡 记忆口诀:
- REQUIRED: 有事务加入,无事务新建
- REQUIRES_NEW: 挂起旧事务,总是新建
- NESTED: 嵌套子事务,父影响子,子不影响父
- SUPPORTS: 有事务支持,无事务也行
- NOT_SUPPORTED: 挂起事务,非事务执行
3.2 Spring事务什么时候会失效?
✅ 正确回答思路:
这是个常见的坑,很多人踩过!我总结了7种情况:
1. 方法不是public
java
@Service
public class UserService {
// ❌ private方法,事务失效
@Transactional
private void addUser(User user) {
userDao.insert(user);
}
// ❌ protected方法,事务失效
@Transactional
protected void updateUser(User user) {
userDao.update(user);
}
}
原因 : 基于代理机制的 Spring AOP 默认只对 public 方法生效,
这是由于动态代理的限制(不包含 AspectJ 编译期织入)。
解决: 改成public。
2. 方法被final修饰
java
@Service
public class UserService {
// ❌ final方法,事务失效(CGLIB代理)
@Transactional
public final void addUser(User user) {
userDao.insert(user);
}
}
原因 : CGLIB是通过生成子类来代理的,final方法无法被子类重写。如果类被 final 修饰,CGLIB 无法生成子类代理,事务同样会失效。
解决: 去掉final。
3. 类内部调用
java
@Service
public class UserService {
public void method1() {
this.method2(); // ❌ this是目标对象,不是代理对象,事务失效
}
@Transactional
public void method2() {
userDao.insert(user);
}
}
原因 : this.method2()调用的是目标对象的方法,不是代理对象的方法,不会走AOP代理。
解决:
java
@Service
public class UserService {
@Autowired
private ApplicationContext context;
public void method1() {
// ✅ 从Spring容器获取代理对象
UserService proxy = context.getBean(UserService.class);
proxy.method2();
}
@Transactional
public void method2() {
userDao.insert(user);
}
}
4. 异常被捕获了
java
@Service
public class UserService {
@Transactional
public void addUser(User user) {
try {
userDao.insert(user);
int i = 1 / 0; // 抛异常
} catch (Exception e) {
// ❌ 异常被捕获了,Spring不知道,不会回滚
log.error("插入失败", e);
}
}
}
原因 : Spring事务默认只对未捕获的RuntimeException 和Error回滚。
解决:
java
@Transactional
public void addUser(User user) {
try {
userDao.insert(user);
int i = 1 / 0;
} catch (Exception e) {
log.error("插入失败", e);
// ✅ 手动设置回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// 或者抛出异常
throw new RuntimeException(e);
}
}
5. 抛出的异常类型不对
java
@Service
public class UserService {
// ❌ 抛出的是checked异常,不会回滚
@Transactional
public void addUser(User user) throws Exception {
userDao.insert(user);
throw new Exception("插入失败"); // checked异常,不回滚
}
}
原因 : Spring事务默认只回滚RuntimeException 和Error ,不回滚checked异常。
解决:
java
// 方案1: 抛出RuntimeException
@Transactional
public void addUser(User user) {
userDao.insert(user);
throw new RuntimeException("插入失败");
}
// 方案2: 指定回滚的异常类型
@Transactional(rollbackFor = Exception.class)
public void addUser(User user) throws Exception {
userDao.insert(user);
throw new Exception("插入失败"); // 现在会回滚了
}
6. 数据库引擎不支持事务
sql
-- ❌ MyISAM引擎不支持事务
CREATE TABLE user (
id bigint PRIMARY KEY
) ENGINE=MyISAM;
解决: 改成InnoDB引擎。
sql
-- ✅ InnoDB支持事务
CREATE TABLE user (
id bigint PRIMARY KEY
) ENGINE=InnoDB;
7. 没有被Spring管理
java
// ❌ 没有@Service注解,不是Spring Bean
public class UserService {
@Transactional
public void addUser(User user) {
userDao.insert(user);
}
}
解决: 加上@Service等注解,让Spring管理。
java
// ✅ 被Spring管理
@Service
public class UserService {
@Transactional
public void addUser(User user) {
userDao.insert(user);
}
}
8. 切面优先级顺序错误
场景 : 如果你在同一个方法上既有 @Transactional,又有自定义的 AOP 切面(比如异常捕获切面)。
原因 : 如果自定义切面的优先级高于事务切面,且自定义切面在 catch 块中吞掉了异常 (没有抛出),事务切面就捕获不到异常,导致无法回滚。
解决:
- 确保异常在自定义切面中被抛出。
- 使用
@Order控制切面顺序,让事务切面在最外层。
9. 传播行为设置错误
java
@Service
public class UserService {
// ❌ NOT_SUPPORTED表示不支持事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addUser(User user) {
userDao.insert(user);
}
}
解决: 检查传播行为配置。
10. 多线程调用
java
@Service
public class UserService {
@Transactional
public void addUser(User user) {
userDao.insert(user);
// ❌ 新线程里调用方法,事务失效
new Thread(() -> {
this.updateUser(user); // 不在同一个线程,事务失效
}).start();
}
@Transactional
public void updateUser(User user) {
userDao.update(user);
}
}
原因 : Spring事务是通过ThreadLocal实现的,不同线程的事务是隔离的。
解决: 不要在事务方法里开新线程,或者在新线程里重新开启事务。
总结图表:
| 失效原因 | 解决办法 |
|---|---|
| 方法不是public | 改成public |
| 方法是final | 去掉final |
| 类内部调用 | 从Spring容器获取代理对象 |
| 异常被捕获 | 手动设置回滚或抛出异常 |
| 异常类型不对 | 指定rollbackFor |
| 数据库不支持事务 | 改用InnoDB引擎 |
| 没有被Spring管理 | 加@Service等注解 |
| 传播行为设置错误 | 检查propagation配置 |
| 多线程调用 | 避免多线程或重新开启事务 |
💡 记忆口诀:
- public方法才有效
- final方法代理不了
- 内部调用不走代理
- 捕获异常要手动回滚
- checked异常要配置
- MyISAM不支持事务
- 类要被Spring管理
- 多线程事务隔离
📌 四、Spring MVC篇
4.1 Spring MVC的工作流程是什么?
✅ 正确回答思路:
SpringMVC是面试必考的,我画图加代码来说明:
一、SpringMVC的核心组件
- DispatcherServlet(前端控制器): 核心,负责调度整个流程
- HandlerMapping(处理器映射器): 根据URL找到对应的Handler(Controller)
- HandlerAdapter(处理器适配器): 执行Handler
- ViewResolver(视图解析器): 解析视图名称,返回View对象
二、完整的请求流程
用户浏览器
↓ 1. 发送请求 http://localhost:8080/user/1
DispatcherServlet(前端控制器)
↓ 2. 询问: 哪个Handler处理这个请求?
HandlerMapping(处理器映射器)
↓ 3. 回答: UserController的getUser方法
DispatcherServlet
↓ 4. 调用Handler
HandlerAdapter(处理器适配器)
↓ 5. 执行UserController.getUser()
UserController
↓ 6. 返回ModelAndView(逻辑视图名"user", 模型数据user对象)
DispatcherServlet
↓ 7. 询问: "user"对应哪个视图文件?
ViewResolver(视图解析器)
↓ 8. 回答: /WEB-INF/views/user.jsp
DispatcherServlet
↓ 9. 渲染视图
View(JSP)
↓ 10. 返回HTML
用户浏览器
↓ 11. 显示页面
三、详细的9个步骤
步骤1: 用户发送请求
GET http://localhost:8080/user/1
步骤2: DispatcherServlet拦截请求
DispatcherServlet是一个Servlet,配置在web.xml中拦截所有请求:
xml
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
步骤3: HandlerMapping查找Handler
DispatcherServlet调用HandlerMapping,根据URL找到对应的Handler:
java
// 伪代码
HandlerExecutionChain handler = handlerMapping.getHandler(request);
// 返回: UserController.getUser()方法 + 拦截器链
Spring MVC有多种HandlerMapping:
- RequestMappingHandlerMapping: 处理@RequestMapping注解(最常用)
- BeanNameUrlHandlerMapping: 根据Bean名称映射URL
步骤4: 选择HandlerAdapter
DispatcherServlet选择合适的HandlerAdapter来执行Handler:
java
// 伪代码
HandlerAdapter adapter = getHandlerAdapter(handler);
常见的HandlerAdapter:
- RequestMappingHandlerAdapter: 处理@RequestMapping注解的方法
- HttpRequestHandlerAdapter: 处理HttpRequestHandler
- SimpleControllerHandlerAdapter: 处理Controller接口
步骤5: 执行拦截器preHandle
HandlerInterceptor 的三个核心方法:
- preHandle:方法执行前
- postHandle:方法执行后、视图渲染前
- afterCompletion:整个请求完成后(常用于资源清理)
java
// 执行拦截器链的preHandle方法
for (HandlerInterceptor interceptor : interceptors) {
if (!interceptor.preHandle(request, response, handler)) {
return; // 返回false,中断请求
}
}
步骤6: HandlerAdapter执行Handler
java
// 伪代码
ModelAndView mav = adapter.handle(request, response, handler);
实际执行Controller的方法:
java
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ModelAndView getUser(@PathVariable Long id) {
User user = userService.getUser(id);
ModelAndView mav = new ModelAndView();
mav.setViewName("user"); // 逻辑视图名
mav.addObject("user", user); // 模型数据
return mav;
}
}
步骤7: 执行拦截器postHandle
java
// 执行拦截器链的postHandle方法
for (HandlerInterceptor interceptor : interceptors) {
interceptor.postHandle(request, response, handler, mav);
}
步骤8: ViewResolver解析视图
DispatcherServlet拿到逻辑视图名"user",调用ViewResolver解析:
java
// 伪代码
View view = viewResolver.resolveViewName("user", locale);
// 返回: JstlView(/WEB-INF/views/user.jsp)
配置ViewResolver:
java
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/"); // 前缀
resolver.setSuffix(".jsp"); // 后缀
return resolver;
// "user" → "/WEB-INF/views/user.jsp"
}
步骤9: 渲染视图
java
// 把模型数据填充到视图
view.render(mav.getModelMap(), request, response);
JSP渲染HTML,返回给浏览器。
步骤10: 执行拦截器afterCompletion
java
// 执行拦截器链的afterCompletion方法
for (HandlerInterceptor interceptor : interceptors) {
interceptor.afterCompletion(request, response, handler, ex);
}
四、现代的RESTful API (最常用)
现在我们大多用 @RestController,它相当于 @Controller + @ResponseBody。流程的区别在于:
- 不走 ViewResolver: 方法返回的对象不被视为视图名称。
- HttpMessageConverter : DispatcherServlet 会利用
HttpMessageConverter(如MappingJackson2HttpMessageConverter) 将对象序列化为 JSON。 - 直接写入响应 : 序列化后的 JSON 直接写入
HttpServletResponse的输出流。
五、拦截器vs过滤器
| 对比项 | 过滤器(Filter) | 拦截器(Interceptor) |
|---|---|---|
| 所属 | Servlet规范 | Spring MVC |
| 拦截范围 | 所有请求 | 只拦截Controller |
| 执行时机 | DispatcherServlet之前 | DispatcherServlet之后,Handler之前 |
| IOC容器 | 不能直接注入Spring Bean | 可以注入Spring Bean |
| 应用场景 | 编码、跨域、认证 | 权限校验、日志、参数校验 |
实际项目经验:
我们项目用拦截器实现了:
1. 登录校验
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 从请求头获取token
String token = request.getHeader("Authorization");
if (token == null || !isValidToken(token)) {
response.setStatus(401);
return false; // 拦截请求
}
// 把用户信息放入ThreadLocal
User user = getUserByToken(token);
UserContext.setCurrentUser(user);
return true; // 放行
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 清理ThreadLocal
UserContext.clear();
}
}
// 配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**") // 拦截/api/**
.excludePathPatterns("/api/login", "/api/register"); // 排除登录、注册接口
}
}
💡 总结: SpringMVC的核心流程:
- DispatcherServlet拦截请求
- HandlerMapping找Handler
- HandlerAdapter执行Handler
- ViewResolver解析视图(RESTful API不需要)
- 渲染视图,返回响应
💡 面试技巧: 可以画个流程图,更直观!
📌 五、Spring Boot篇
5.1 Spring Boot的自动配置原理是什么?
✅ 正确回答思路:
Spring Boot的自动配置是它最核心的特性,我从原理到源码来说明:
一、什么是自动配置?
传统Spring项目:
xml
<!-- 要配置数据源 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 要配置SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 要配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置很多很多... -->
Spring Boot项目:
yaml
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
就这么简单! Spring Boot自动帮我们配置了DataSource、SqlSessionFactory、TransactionManager等所有需要的Bean。
二、自动配置的核心原理
核心注解 : @SpringBootApplication
java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@SpringBootApplication是个组合注解:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 相当于@Configuration
@EnableAutoConfiguration // 核心!自动配置的关键
@ComponentScan // 扫描当前包及子包的@Component等注解
public @interface SpringBootApplication {
}
关键就是@EnableAutoConfiguration:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 自动配置包
@Import(AutoConfigurationImportSelector.class) // 核心!导入自动配置类
public @interface EnableAutoConfiguration {
}
三、AutoConfigurationImportSelector做了什么?
这个类负责导入所有的自动配置类:
java
public class AutoConfigurationImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 获取所有的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 2. 去重
configurations = removeDuplicates(configurations);
// 3. 排除不需要的
configurations = exclude(configurations);
// 4. 过滤(根据条件注解@ConditionalOnXxx)
configurations = filter(configurations);
// 5. 返回最终的自动配置类列表
return configurations.toArray(new String[0]);
}
protected List<String> getCandidateConfigurations() {
// 从META-INF/spring.factories文件读取配置类
return SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class,
classLoader
);
}
}
四、spring.factories文件
Spring Boot在各个starter的jar包里都有一个META-INF/spring.factories文件:
properties
# spring-boot-autoconfigure-3.x.x.jar里的spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
... (一共130多个自动配置类)
Spring Boot启动时会读取所有jar包里的这个文件,加载所有的自动配置类。
五、条件注解(@ConditionalOnXxx)
但是!不是所有自动配置类都会生效,要看条件注解。
以DataSourceAutoConfiguration为例:
java
@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class}) // 类路径有DataSource类才生效
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory") // 没有R2DBC的ConnectionFactory才生效
@EnableConfigurationProperties(DataSourceProperties.class) // 启用配置属性
@Import({DataSourcePoolMetadataProvidersConfiguration.class})
public class DataSourceAutoConfiguration {
@Configuration
@Conditional(PooledDataSourceCondition.class) // 有数据库连接池才生效
@ConditionalOnMissingBean({DataSource.class, XADataSource.class}) // 用户没有自定义DataSource才生效
@Import({HikariConfiguration.class, Tomcat.class, Dbcp2.class, ...})
protected static class PooledDataSourceConfiguration {
}
@Configuration
@ConditionalOnClass(HikariDataSource.class) // 有HikariCP才生效
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)
static class HikariConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
// ... 配置数据源
return dataSource;
}
}
}
常用的条件注解:
| 注解 | 作用 |
|---|---|
| @ConditionalOnClass | 类路径有指定的类才生效 |
| @ConditionalOnMissingClass | 类路径没有指定的类才生效 |
| @ConditionalOnBean | 容器有指定的Bean才生效 |
| @ConditionalOnMissingBean | 容器没有指定的Bean才生效 |
| @ConditionalOnProperty | 配置文件有指定的属性才生效 |
| @ConditionalOnResource | 类路径有指定的资源才生效 |
| @ConditionalOnWebApplication | 是Web应用才生效 |
| @ConditionalOnNotWebApplication | 不是Web应用才生效 |
六、自动配置的完整流程
1. @SpringBootApplication启动
↓
2. @EnableAutoConfiguration生效
↓
3. AutoConfigurationImportSelector加载META-INF/spring.factories
↓
4. 读取所有的自动配置类(130多个)
↓
5. 根据@ConditionalOnXxx条件注解过滤
↓
6. 生效的自动配置类创建Bean
↓
7. 读取application.yml的配置(@ConfigurationProperties)
↓
8. 注入到Bean的属性
↓
9. 自动配置完成!
七、自定义一个Starter
理解了原理,我们也可以自己写一个Starter:
1. 创建starter项目
xml
<!-- my-spring-boot-starter/pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
2. 创建配置属性类
java
@ConfigurationProperties(prefix = "my.service")
public class MyServiceProperties {
private String name;
private int timeout = 3000;
// getter/setter
}
3. 创建Service
java
public class MyService {
private String name;
private int timeout;
public MyService(String name, int timeout) {
this.name = name;
this.timeout = timeout;
}
public void doSomething() {
System.out.println("MyService: " + name + ", timeout: " + timeout);
}
}
4. 创建自动配置类
java
@Configuration
@ConditionalOnClass(MyService.class) // 类路径有MyService才生效
@EnableConfigurationProperties(MyServiceProperties.class) // 启用配置属性
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 用户没有自定义MyService才生效
public MyService myService(MyServiceProperties properties) {
return new MyService(properties.getName(), properties.getTimeout());
}
}
5. 创建spring.factories
properties
# src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.MyServiceAutoConfiguration
6. 使用Starter
其他项目引入我们的starter:
xml
<dependency>
<groupId>com.example</groupId>
<artifactId>my-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
配置文件:
yaml
my:
service:
name: MyService
timeout: 5000
使用:
java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@RestController
public class TestController {
@Autowired
private MyService myService; // 自动注入!
@GetMapping("/test")
public String test() {
myService.doSomething();
return "OK";
}
}
八、实际项目经验
在我的项目中,我们自定义了一个统一日志Starter:
java
@Configuration
@EnableConfigurationProperties(LogProperties.class)
public class LogAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LogAspect logAspect(LogProperties properties) {
return new LogAspect(properties);
}
@Bean
@ConditionalOnProperty(name = "log.storage.enabled", havingValue = "true")
public LogStorage logStorage() {
return new LogStorage();
}
}
所有微服务只需要引入这个starter,就自动具备了日志记录功能,不需要重复配置。
💡 总结: Spring Boot自动配置的核心:
- @EnableAutoConfiguration触发自动配置
- AutoConfigurationImportSelector加载配置类
- spring.factories文件列出所有配置类
- @ConditionalOnXxx条件注解决定是否生效
- @ConfigurationProperties读取配置文件
💡 面试加分项: "Spring Boot的自动配置体现了约定优于配置的思想,通过智能的默认配置,让开发者专注于业务逻辑,而不是配置文件。"
5.2 Spring Boot的启动流程是什么?
✅ 正确回答思路:
一、启动入口
java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
这一行代码做了很多事情!
二、SpringApplication的创建
java
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return new SpringApplication(primarySource).run(args);
}
分两步:
- 创建SpringApplication对象
- 调用run方法
创建SpringApplication对象做了什么?
java
public SpringApplication(Class<?>... primarySources) {
// 1. 保存主配置类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 2. 判断应用类型(Servlet、Reactive、None)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 3. 加载ApplicationContextInitializer(从spring.factories)
setInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 4. 加载ApplicationListener(从spring.factories)
setListeners(getSpringFactoriesInstances(ApplicationListener.class));
// 5. 推断主类(有main方法的类)
this.mainApplicationClass = deduceMainApplicationClass();
}
三、run方法的执行流程
java
public ConfigurableApplicationContext run(String... args) {
// 1. 创建StopWatch,用于统计启动耗时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2. 创建Bootstrap上下文
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 3. 配置Headless属性(java.awt.headless)
configureHeadlessProperty();
// 4. 获取SpringApplicationRunListeners(启动监听器)
SpringApplicationRunListeners listeners = getRunListeners(args);
// 5. 发布ApplicationStartingEvent事件
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 6. 封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 7. 准备环境(Environment)
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 8. 打印Banner
Banner printedBanner = printBanner(environment);
// 9. 创建ApplicationContext(IOC容器)
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 10. 准备ApplicationContext
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 11. 刷新ApplicationContext(核心!)
refreshContext(context);
// 12. 刷新后的处理
afterRefresh(context, applicationArguments);
// 13. 停止计时
stopWatch.stop();
// 14. 打印启动日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 15. 发布ApplicationStartedEvent事件
listeners.started(context);
// 16. 调用ApplicationRunner和CommandLineRunner
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 17. 发布ApplicationReadyEvent事件
listeners.ready(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
// 18. 返回ApplicationContext
return context;
}
详细说明几个关键步骤:
步骤7: 准备环境(prepareEnvironment)
java
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ...) {
// 1. 创建Environment对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. 配置Environment(命令行参数、系统属性)
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 3. 发布ApplicationEnvironmentPreparedEvent事件
listeners.environmentPrepared(bootstrapContext, environment);
// 4. 绑定环境到SpringApplication
bindToSpringApplication(environment);
return environment;
}
Environment是什么?
- 包含所有的配置信息:系统属性、环境变量、配置文件(application.yml)
- 通过
@Value和@ConfigurationProperties注入的值都来自Environment
步骤9: 创建ApplicationContext
根据应用类型创建不同的容器:
java
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
switch (this.webApplicationType) {
case SERVLET:
// Web应用,创建AnnotationConfigServletWebServerApplicationContext
contextClass = AnnotationConfigServletWebServerApplicationContext.class;
break;
case REACTIVE:
// 响应式应用
contextClass = AnnotationConfigReactiveWebServerApplicationContext.class;
break;
default:
// 非Web应用
contextClass = AnnotationConfigApplicationContext.class;
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
步骤10: 准备ApplicationContext
java
private void prepareContext(...) {
// 1. 设置Environment
context.setEnvironment(environment);
// 2. 后置处理ApplicationContext
postProcessApplicationContext(context);
// 3. 执行ApplicationContextInitializer
applyInitializers(context);
// 4. 发布ApplicationContextInitializedEvent事件
listeners.contextPrepared(context);
// 5. 注册启动参数Bean
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
// 6. 加载配置源(主配置类)
Set<Object> sources = getAllSources();
load(context, sources.toArray(new Object[0]));
// 7. 发布ApplicationPreparedEvent事件
listeners.contextLoaded(context);
}
步骤11: 刷新ApplicationContext(核心!)
java
private void refreshContext(ConfigurableApplicationContext context) {
// 调用AbstractApplicationContext的refresh方法
refresh(context);
}
这一步会:
- 创建BeanFactory
- 加载BeanDefinition
- 实例化所有单例Bean
- 初始化Bean
- 自动配置生效
- 启动内嵌的Tomcat
这就是IOC容器的启动流程!(前面讲过的1.3节)
步骤16: 调用Runner
Spring Boot支持在启动完成后执行一些初始化逻辑:
java
@Component
@Order(1) // 指定执行顺序
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner执行了!");
// 可以在这里做一些初始化工作:预热缓存、加载数据等
}
}
@Component
@Order(2)
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner执行了!");
}
}
区别:
- ApplicationRunner: 参数是ApplicationArguments(封装了命令行参数)
- CommandLineRunner: 参数是String数组(原始命令行参数)
四、启动流程图
1. 创建SpringApplication对象
- 判断应用类型
- 加载初始化器和监听器
↓
2. 执行run方法
↓
3. 准备Environment
- 读取配置文件
- 设置环境变量
↓
4. 打印Banner
↓
5. 创建ApplicationContext
↓
6. 准备ApplicationContext
- 注册配置类
- 执行初始化器
↓
7. 刷新ApplicationContext(核心!)
- 创建BeanFactory
- 加载BeanDefinition
- 实例化Bean
- 自动配置
- 启动Tomcat
↓
8. 调用Runner
↓
9. 发布ApplicationReadyEvent
↓
10. 启动完成!
五、实际项目经验
1. 自定义Banner
# src/main/resources/banner.txt
__ __ _ _ _ _
| \/ |_ _ / \ _ __ _ __ | |_ ___ | |_(_) ___ _ __
| |\/| | | | |/ _ \ | '_ \| '_ \ | __| / __| | __| |/ _ \| '_ \
| | | | |_| / ___ \| |_) | |_) || |_ | (__ | |_| | (_) | | | |
|_| |_|\__, /_/ \_\ .__/| .__/ \__| \___| \__|_|\___/|_| |_|
|___/ |_| |_|
Application Version: ${application.version}
Spring Boot Version: ${spring-boot.version}
2. 监听启动完成事件
java
@Component
public class StartupListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("应用启动完成,开始预热缓存...");
// 预热缓存
cacheService.warmUp();
}
}
3. 启动失败诊断
Spring Boot提供了FailureAnalyzer接口,可以分析启动失败的原因:
java
public class MyFailureAnalyzer extends AbstractFailureAnalyzer<MyException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, MyException cause) {
return new FailureAnalysis(
"数据库连接失败: " + cause.getMessage(),
"请检查application.yml中的数据库配置",
cause
);
}
}
💡 总结: Spring Boot启动流程:
- 创建SpringApplication对象(加载初始化器、监听器)
- 准备Environment(读取配置文件)
- 创建ApplicationContext(IOC容器)
- 刷新ApplicationContext(实例化Bean、自动配置、启动Tomcat)
- 调用Runner(初始化逻辑)
- 启动完成
💡 面试技巧: 可以说"Spring Boot的启动流程体现了模板方法模式,通过监听器和Runner提供了多个扩展点,让开发者可以在启动的不同阶段插入自定义逻辑。"
📌 六、Bean生命周期篇
6.1 Spring Bean的生命周期是什么?
✅ 正确回答思路:
这是Spring面试的高频题,我从创建到销毁完整讲解:
一、Bean生命周期概览
1. 实例化Bean(Instantiation)
2. 设置属性值(Populate Properties)
3. 检查Aware接口(Aware接口回调)
4. BeanPostProcessor前置处理
5. 初始化Bean(Initialization)
6. BeanPostProcessor后置处理
7. Bean可用(Ready to use)
8. 销毁Bean(Destruction)
二、详细的生命周期
阶段1: 实例化Bean
Spring通过反射调用构造器创建Bean对象:
java
// 伪代码
Class<?> clazz = Class.forName("com.example.User");
Object bean = clazz.getDeclaredConstructor().newInstance();
可以通过InstantiationAwareBeanPostProcessor拦截:
java
@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
System.out.println("实例化前: " + beanName);
return null; // 返回null表示继续正常流程
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) {
System.out.println("实例化后: " + beanName);
return true; // 返回false会跳过属性赋值
}
}
阶段2: 设置属性值
Spring通过反射给Bean的属性赋值:
java
@Component
public class UserService {
@Autowired
private UserDao userDao; // Spring会通过反射注入
@Value("${app.name}")
private String appName; // 从配置文件注入
}
阶段3: Aware接口回调
如果Bean实现了某些Aware接口,Spring会回调这些方法:
java
@Component
public class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
private String beanName;
private BeanFactory beanFactory;
private ApplicationContext applicationContext;
@Override
public void setBeanName(String name) {
System.out.println("1. BeanNameAware: " + name);
this.beanName = name;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
System.out.println("2. BeanFactoryAware");
this.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
System.out.println("3. ApplicationContextAware");
this.applicationContext = applicationContext;
}
}
常用的Aware接口:
| Aware接口 | 回调方法 | 作用 |
|---|---|---|
| BeanNameAware | setBeanName(String name) | 获取Bean的名称 |
| BeanFactoryAware | setBeanFactory(BeanFactory) | 获取BeanFactory |
| ApplicationContextAware | setApplicationContext(ApplicationContext) | 获取ApplicationContext |
| EnvironmentAware | setEnvironment(Environment) | 获取Environment |
| ResourceLoaderAware | setResourceLoader(ResourceLoader) | 获取资源加载器 |
阶段4: BeanPostProcessor前置处理
java
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("初始化前: " + beanName);
// 可以返回代理对象
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("初始化后: " + beanName);
// AOP代理就是在这里创建的!
return bean;
}
}
阶段5: 初始化Bean
有三种方式定义初始化方法,按顺序执行:
java
@Component
public class User implements InitializingBean {
// 方式1: @PostConstruct注解
@PostConstruct
public void postConstruct() {
System.out.println("1. @PostConstruct");
}
// 方式2: 实现InitializingBean接口
@Override
public void afterPropertiesSet() {
System.out.println("2. InitializingBean.afterPropertiesSet()");
}
// 方式3: 自定义init方法
public void initMethod() {
System.out.println("3. init-method");
}
}
// 配置init-method
@Configuration
public class AppConfig {
@Bean(initMethod = "initMethod")
public User user() {
return new User();
}
}
执行顺序: @PostConstruct → afterPropertiesSet() → init-method
阶段6: BeanPostProcessor后置处理
java
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// AOP代理在这里创建
if (需要AOP增强) {
return 创建代理对象(bean);
}
return bean;
}
阶段7: Bean可用
初始化完成,Bean可以使用了!
阶段8: 销毁Bean
容器关闭时,销毁Bean:
java
@Component
public class User implements DisposableBean {
// 方式1: @PreDestroy注解
@PreDestroy
public void preDestroy() {
System.out.println("1. @PreDestroy");
}
// 方式2: 实现DisposableBean接口
@Override
public void destroy() {
System.out.println("2. DisposableBean.destroy()");
}
// 方式3: 自定义destroy方法
public void destroyMethod() {
System.out.println("3. destroy-method");
}
}
// 配置destroy-method
@Bean(destroyMethod = "destroyMethod")
public User user() {
return new User();
}
执行顺序: @PreDestroy → destroy() → destroy-method
三、完整的生命周期代码示例
java
@Component
public class LifecycleBean implements
BeanNameAware,
BeanFactoryAware,
ApplicationContextAware,
InitializingBean,
DisposableBean {
private String beanName;
public LifecycleBean() {
System.out.println("1. 构造器");
}
@Value("${app.name:DefaultApp}")
public void setAppName(String appName) {
System.out.println("2. 设置属性: appName = " + appName);
}
@Override
public void setBeanName(String name) {
System.out.println("3. BeanNameAware: " + name);
this.beanName = name;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
System.out.println("4. BeanFactoryAware");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
System.out.println("5. ApplicationContextAware");
}
// BeanPostProcessor.postProcessBeforeInitialization在这里执行
@PostConstruct
public void postConstruct() {
System.out.println("6. @PostConstruct");
}
@Override
public void afterPropertiesSet() {
System.out.println("7. InitializingBean.afterPropertiesSet()");
}
public void initMethod() {
System.out.println("8. init-method");
}
// BeanPostProcessor.postProcessAfterInitialization在这里执行
// Bean可用!
@PreDestroy
public void preDestroy() {
System.out.println("9. @PreDestroy");
}
@Override
public void destroy() {
System.out.println("10. DisposableBean.destroy()");
}
public void destroyMethod() {
System.out.println("11. destroy-method");
}
}
// 配置
@Configuration
public class AppConfig {
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public LifecycleBean lifecycleBean() {
return new LifecycleBean();
}
}
// BeanPostProcessor
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof LifecycleBean) {
System.out.println("5.5 BeanPostProcessor.postProcessBeforeInitialization");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof LifecycleBean) {
System.out.println("8.5 BeanPostProcessor.postProcessAfterInitialization");
}
return bean;
}
}
输出:
1. 构造器
2. 设置属性: appName = MyApp
3. BeanNameAware: lifecycleBean
4. BeanFactoryAware
5. ApplicationContextAware
5.5 BeanPostProcessor.postProcessBeforeInitialization
6. @PostConstruct
7. InitializingBean.afterPropertiesSet()
8. init-method
8.5 BeanPostProcessor.postProcessAfterInitialization
... Bean可用 ...
9. @PreDestroy
10. DisposableBean.destroy()
11. destroy-method
四、实际项目应用
1. 初始化连接池
java
@Component
public class RedisConnectionPool {
private JedisPool jedisPool;
@PostConstruct
public void init() {
// 初始化Redis连接池
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(10);
jedisPool = new JedisPool(config, "localhost", 6379);
System.out.println("Redis连接池初始化完成");
}
@PreDestroy
public void destroy() {
// 关闭连接池
if (jedisPool != null) {
jedisPool.close();
System.out.println("Redis连接池已关闭");
}
}
}
2. 预加载数据
java
@Component
public class DictionaryService implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private RedisTemplate redisTemplate;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 容器启动完成后,预加载字典数据到Redis
System.out.println("预加载字典数据...");
List<Dictionary> dictionaries = dictionaryDao.selectAll();
for (Dictionary dict : dictionaries) {
redisTemplate.opsForValue().set("dict:" + dict.getCode(), dict);
}
System.out.println("字典数据加载完成");
}
}
3. 获取ApplicationContext
java
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextHolder.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
public static Object getBean(String beanName) {
return applicationContext.getBean(beanName);
}
}
💡 总结: Bean生命周期核心流程:
- 实例化(通过构造器)
- 属性赋值(依赖注入)
- Aware接口回调(获取容器信息)
- BeanPostProcessor前置处理
- 初始化(@PostConstruct → afterPropertiesSet → init-method)
- BeanPostProcessor后置处理(AOP代理)
- Bean可用
- 销毁(@PreDestroy → destroy → destroy-method)
💡 记忆口诀: "实例化、赋属性、Aware回调、前置处理、初始化、后置处理、可用、销毁"