*Java 沉淀重走长征路*之——《IOC、AOP与事务管理深度剖析》
前言:为什么Spring能统治Java开发二十年?
在Java领域,Spring框架几乎成为了企业级开发的代名词。从2004年Rod Johnson发布《Expert One-on-One J2EE Design and Development》并推出Spring Framework 1.0至今,Spring已经走过了近二十年的历程,依然是Java开发生态中不可或缺的核心技术。很多初学者在接触Spring时,往往直接扎进XML配置或者注解的使用,学完之后只知道“怎么用”,却不明白“为什么这么做”,导致遇到问题时无从下手。
本文将采用问题驱动的方式,带你攻克Spring 5的三大核心:IOC(控制反转)、AOP(面向切面编程)以及声明式事务。我们将严格按照六个阶段层层递进,确保你不仅能看懂代码,更能理解背后的设计哲学,并在企业中写出符合规范的代码。
全文约3万字,涵盖从基础概念到源码分析的完整内容,建议收藏后慢慢阅读。
阶段一:问题锚定——没有Spring之前,程序员到底有多痛?
1.1 场景化抛出问题:耦合度爆炸的噩梦
1.1.1 传统J2EE开发的痛点
在Spring框架诞生之前,Java企业级开发主要依赖EJB(Enterprise JavaBeans)。EJB 2.1时代的开发体验可以用“噩梦”来形容:
// 传统的EJB 2.1开发方式
public interface UserService extends EJBObject {
public void addUser(User user) throws RemoteException;
}
public interface UserServiceHome extends EJBHome {
public UserService create() throws CreateException, RemoteException;
}
public class UserServiceBean implements SessionBean {
private SessionContext context;
public void ejbCreate() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext ctx) {
this.context = ctx;
}
public void addUser(User user) {
// 业务逻辑
}
}
这段代码暴露了EJB的几个核心问题:
-
必须实现多个接口:一个简单的业务类需要实现EJBObject、EJBHome、SessionBean等多个接口
-
大量模板方法:即使不需要生命周期管理,也必须实现ejbCreate、ejbRemove等方法
-
强制处理RemoteException:即使本地调用也要处理远程异常
-
部署繁琐:需要编写复杂的ejb-jar.xml部署描述符
1.1.2 手动管理依赖的困境
假设你正在开发一个电商系统的用户服务,在没有Spring的情况下,代码通常是这样的:
public class UserService {
// 直接在代码里new对象
private UserDao userDao = new UserDaoImpl();
private EmailService emailService = new EmailServiceImpl();
private LogService logService = new LogServiceImpl();
public void register(User user) {
userDao.save(user);
emailService.sendWelcomeEmail(user);
logService.log("User registered: " + user.getUsername());
}
}
这段代码看似简单,但隐藏着严重的问题:
痛点一:耦合度极高
-
UserService直接依赖UserDaoImpl的具体实现。如果明天数据库从MySQL换成Oracle,你需要把UserDaoImpl换成UserDaoOracleImpl,那么所有new过这个对象的地方都得改。在一个大型项目中,这可能涉及成百上千处的修改。
痛点二:单元测试困难
public class UserServiceTest {
@Test
public void testRegister() {
UserService userService = new UserService();
// 问题:userService强制加载了真实的UserDao和EmailService
// 无法简单地用Mock对象替换,测试会实际操作数据库、发送邮件
userService.register(new User("test"));
}
}
痛点三:非业务代码入侵
当你想给这个方法添加日志或事务时,必须修改源码,加入大量的模板代码:
public void register(User user) {
// 事务开始
Transaction tx = null;
try {
tx = transactionManager.beginTransaction();
// 日志记录
logger.info("开始注册用户: " + user.getUsername());
// 业务逻辑
userDao.save(user);
emailService.sendWelcomeEmail(user);
// 事务提交
tx.commit();
logger.info("用户注册成功");
} catch (Exception e) {
// 事务回滚
if (tx != null) tx.rollback();
logger.error("用户注册失败", e);
throw e;
}
}
这段代码中,真正的业务逻辑只有一行userDao.save(user),其他都是事务控制、日志记录等“横切关注点”。这些代码散落在各个业务方法中,导致:
-
代码重复率极高
-
维护困难(修改事务策略要改所有方法)
-
业务逻辑难以阅读
1.2 技术定位:Spring是什么?
Spring是一个轻量级的Java开发框架,其核心定位是降低企业级应用开发的复杂性。它通过以下方式解决上述问题:
-
IOC容器:管理对象之间的依赖关系,实现解耦
-
AOP支持:将日志、事务等横切关注点模块化
-
声明式事务:通过注解或配置管理事务,摆脱繁琐的try/catch
Spring框架的核心价值可以概括为一句话:让开发者专注于业务逻辑,而非基础设施。
1.3 Spring框架的演变历程
为了更好地理解Spring,我们需要了解它的发展历程:
| 版本 | 发布时间 | 核心特性 |
|---|---|---|
| Spring 1.0 | 2004年3月 | XML配置、依赖注入、AOP、JDBC支持 |
| Spring 2.0 | 2006年10月 | XML命名空间、AspectJ支持、Java 5注解 |
| Spring 3.0 | 2009年12月 | Java配置、REST支持、表达式语言 |
| Spring 4.0 | 2013年12月 | Java 8支持、WebSocket、条件注解 |
| Spring 5.0 | 2017年9月 | 响应式编程、WebFlux、Kotlin支持 |
Spring 5作为里程碑式版本,引入了响应式编程模型(WebFlux),全面支持Java 8+特性,并对核心容器进行了优化。
1.4 技术边界说明
能做什么:
-
解耦组件(IOC容器)
-
统一事务管理(声明式事务)
-
简化数据库访问(JDBC模板)
-
构建Web层(Spring MVC/WebFlux)
-
集成各种中间件(Redis、MQ、Dubbo等)
不能做什么:
-
Spring本身不直接处理高并发下的数据一致性(那是数据库和分布式锁的事)
-
不替代消息队列(那是RabbitMQ/Kafka的事)
-
不处理大数据存储(那是Hadoop/HBase的事)
Spring是个胶水框架,它的价值在于优雅地粘合各种技术组件,让它们协同工作。
阶段二:基础认知——大白话拆解IOC、AOP与事务
2.1 IOC:控制反转与依赖注入
2.1.1 核心概念拆解(去专业化)
传统模式:你想吃饭,得自己买菜、洗菜、做饭(自己new对象)。这就是“控制”在你自己手中。
IOC模式:你直接去餐馆点餐(声明依赖),服务员(Spring容器)把做好的菜送到你面前(注入对象)。控制权从“你自身”反转给了“容器”。
依赖注入(DI):容器在运行时动态地将某种依赖关系注入到对象中。你不需要知道对象从哪里来、怎么创建,只需要声明“我需要什么”。
2.1.2 依赖注入的三种方式
为了帮助理解,我们通过代码对比来看依赖注入的演进过程:
方式一:构造器注入(Constructor Injection)
@Service
public class UserService {
private final UserDao userDao;
private final EmailService emailService;
// 构造器注入:依赖通过构造函数传入
@Autowired
public UserService(UserDao userDao, EmailService emailService) {
this.userDao = userDao;
this.emailService = emailService;
}
}
优点:依赖不可变、确保不为空、便于单元测试
缺点:当依赖过多时,构造器参数列表会很长
方式二:Setter注入(Setter Injection)
@Service
public class UserService {
private UserDao userDao;
private EmailService emailService;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
优点:可选依赖可以在运行时修改
缺点:依赖可能为null,需要额外检查
方式三:字段注入(Field Injection)
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private EmailService emailService;
}
优点:代码最简洁
缺点:无法创建不可变对象,不利于单元测试
最佳实践:官方推荐使用构造器注入,因为它保证了依赖的不可变性和非空性,同时便于单元测试。
2.1.3 IOC容器的工作原理
Spring IOC容器本质上是一个超级工厂,负责创建、管理对象(称为Bean)。它的核心接口有两个:
-
BeanFactory:最基本的容器,提供基础的DI支持
-
ApplicationContext:BeanFactory的子接口,增加了AOP、国际化、事件发布等企业级功能
容器启动流程:
// 1. 定位:扫描类路径,查找@Component、@Service等注解 // 2. 加载:将注解解析成BeanDefinition(Bean的定义信息) // 3. 注册:将BeanDefinition注册到容器中 // 4. 依赖注入:实例化Bean,并根据@Autowired进行依赖注入 // 5. 初始化:执行InitializingBean、@PostConstruct等初始化方法 // 6. 就绪:Bean准备就绪,等待使用
2.2 AOP:面向切面编程
2.2.1 从生活场景理解AOP
想象一下你是一家餐厅的老板,你想记录每个顾客点餐的时间。传统做法是:每个服务员在点餐时,先看一眼手表,记录时间,然后点餐,再看一眼手表,记录结束时间。
这会导致两个问题:
-
服务员的工作变得复杂(要记时间)
-
如果以后想改成记录顾客的性别,每个服务员都要改
AOP的做法是:请一个专门的“记录员”,站在服务员旁边,当服务员开始点餐时自动记录开始时间,点餐结束时自动记录结束时间。服务员完全不知道有记录这件事,继续专心点餐。
2.2.2 AOP核心术语(必须掌握的5个概念)
| 术语 | 英文 | 类比理解 |
|---|---|---|
| 切面 | Aspect | 记录员这个角色本身,即横切关注点的模块化 |
| 连接点 | JoinPoint | 餐厅里所有可能的记录时刻(每个顾客点餐、结账、投诉等) |
| 切入点 | Pointcut | 只记录“点餐”这个动作,定义哪些连接点会被拦截 |
| 通知 | Advice | 记录员具体做什么(记录开始时间、结束时间) |
| 织入 | Weaving | 把记录员安排到服务员旁边的过程 |
2.2.3 五种通知类型
Spring AOP支持五种类型的通知:
@Aspect
@Component
public class LoggingAspect {
// 1. 前置通知:在方法执行前执行
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("前置通知: " + joinPoint.getSignature().getName());
}
// 2. 后置通知:在方法执行后执行(无论是否异常)
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("后置通知: 方法执行完毕");
}
// 3. 返回通知:在方法成功返回后执行
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
System.out.println("返回通知: 返回值 = " + result);
}
// 4. 异常通知:在方法抛出异常后执行
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
public void afterThrowingAdvice(JoinPoint joinPoint, Throwable error) {
System.out.println("异常通知: 异常信息 = " + error.getMessage());
}
// 5. 环绕通知:最强大,可以控制方法是否执行、修改返回值、处理异常
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知 - 开始: " + System.currentTimeMillis());
try {
// 执行目标方法
Object result = joinPoint.proceed();
System.out.println("环绕通知 - 结束: " + System.currentTimeMillis());
return result;
} catch (Exception e) {
System.out.println("环绕通知 - 异常: " + e.getMessage());
throw e;
}
}
}
2.3 声明式事务
2.3.1 事务的四大特性(ACID)
| 特性 | 英文 | 含义 |
|---|---|---|
| 原子性 | Atomicity | 事务中的所有操作要么全部成功,要么全部失败回滚 |
| 一致性 | Consistency | 事务执行前后,数据完整性约束保持一致 |
| 隔离性 | Isolation | 并发事务之间互不干扰 |
| 持久性 | Durability | 事务提交后,对数据的修改是永久性的 |
2.3.2 Spring事务管理的发展历程
阶段一:编程式事务(JDBC时代)
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 开启事务
// 业务操作
PreparedStatement ps1 = conn.prepareStatement("UPDATE account SET balance = balance - 100 WHERE id = 1");
ps1.executeUpdate();
PreparedStatement ps2 = conn.prepareStatement("UPDATE account SET balance = balance + 100 WHERE id = 2");
ps2.executeUpdate();
conn.commit(); // 提交事务
} catch (Exception e) {
if (conn != null) conn.rollback(); // 回滚事务
throw e;
} finally {
if (conn != null) conn.close();
}
阶段二:模板式事务(Spring JdbcTemplate)
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
jdbcTemplate.update("UPDATE account SET balance = balance - 100 WHERE id = 1");
jdbcTemplate.update("UPDATE account SET balance = balance + 100 WHERE id = 2");
} catch (Exception e) {
status.setRollbackOnly(); // 标记回滚
throw e;
}
}
});
阶段三:声明式事务(现代方式)
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
accountDao.decreaseBalance(fromId, amount);
accountDao.increaseBalance(toId, amount);
}
2.4 最小可运行示例(Hello World级)
为了让你快速体验Spring的魅力,我们搭建一个最简化的示例项目。
2.4.1 项目结构
spring-helloworld/
├── pom.xml
└── src/main/java/
└── com/example/
├── AppConfig.java
├── UserDao.java
├── UserService.java
└── Application.java
2.4.2 Maven依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring-helloworld</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.3.23</spring.version>
</properties>
<dependencies>
<!-- Spring核心容器 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.4.3 核心代码
UserDao.java - 数据访问层
package com.example;
import org.springframework.stereotype.Repository;
@Repository // 标识这是一个DAO组件
public class UserDao {
public void save() {
System.out.println("保存用户到数据库 - " + this.hashCode());
}
public void update() {
System.out.println("更新用户信息");
}
public void delete() {
System.out.println("删除用户");
}
public String findById(Long id) {
return "User{id=" + id + ", name='张三'}";
}
}
UserService.java - 业务层
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service // 标识这是一个Service组件
public class UserService {
@Autowired // 依赖注入:容器自动把UserDao传进来
private UserDao userDao;
public void addUser() {
System.out.println("业务层处理用户添加...");
userDao.save();
}
public String getUser(Long id) {
System.out.println("业务层查询用户,ID: " + id);
return userDao.findById(id);
}
}
AppConfig.java - 配置类
package com.example;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration // 标识这是一个配置类
@ComponentScan("com.example") // 扫描指定包下的所有Spring注解
public class AppConfig {
// 可以在这里定义其他Bean,比如数据源、事务管理器等
}
Application.java - 启动类
package com.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
public static void main(String[] args) {
System.out.println("=== Spring容器启动 ===");
// 1. 创建Spring容器(基于注解配置)
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("=== Spring容器启动完成 ===\n");
// 2. 从容器获取Bean
UserService userService = context.getBean(UserService.class);
// 3. 使用Bean执行业务
System.out.println("=== 执行业务方法 ===");
userService.addUser();
System.out.println("\n=== 再次获取同一个Bean ===");
UserService anotherService = context.getBean(UserService.class);
System.out.println("两个UserService是同一个对象吗? " +
(userService == anotherService)); // true,默认单例
System.out.println("\n=== 查看容器中的所有Bean ===");
String[] beanNames = context.getBeanDefinitionNames();
System.out.println("容器中的Bean(共" + beanNames.length + "个):");
for (String name : beanNames) {
System.out.println(" - " + name);
}
}
}
2.4.4 运行结果
=== Spring容器启动 === === Spring容器启动完成 === === 执行业务方法 === 业务层处理用户添加... 保存用户到数据库 - 12345678 === 再次获取同一个Bean === 两个UserService是同一个对象吗? true === 查看容器中的所有Bean === 容器中的Bean(共6个): - org.springframework.context.annotation.internalConfigurationAnnotationProcessor - org.springframework.context.annotation.internalAutowiredAnnotationProcessor - org.springframework.context.annotation.internalCommonAnnotationProcessor - org.springframework.context.event.internalEventListenerProcessor - org.springframework.context.event.internalEventListenerFactory - appConfig - userDao - userService
2.5 核心原理极简讲解
2.5.1 IOC原理:从XML到注解的演进
传统XML配置方式(Spring 1.x):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义Bean -->
<bean id="userDao" class="com.example.UserDao"/>
<!-- 定义Bean并注入依赖 -->
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
</bean>
</beans>
注解配置方式(Spring 2.5+):
@Component
public class UserDao { }
@Component
public class UserService {
@Autowired
private UserDao userDao;
}
Java配置方式(Spring 3.0+):
@Configuration
public class AppConfig {
@Bean
public UserDao userDao() {
return new UserDao();
}
@Bean
public UserService userService() {
UserService service = new UserService();
service.setUserDao(userDao());
return service;
}
}
2.5.2 Spring容器的核心接口
BeanFactory:最基础的容器,提供getBean()等基础方法
public interface BeanFactory {
Object getBean(String name);
<T> T getBean(Class<T> requiredType);
boolean containsBean(String name);
boolean isSingleton(String name);
boolean isPrototype(String name);
// ...
}
ApplicationContext:BeanFactory的增强版,增加了企业级功能
public interface ApplicationContext extends BeanFactory {
// 国际化支持
String getMessage(String code, Object[] args, Locale locale);
// 事件发布
void publishEvent(ApplicationEvent event);
// 资源访问
Resource getResource(String location);
// 环境信息
Environment getEnvironment();
}
2.5.3 Bean的生命周期(10个阶段)
Spring Bean从创建到销毁经历了复杂的生命周期:
@Component
public class LifecycleBean implements BeanNameAware, BeanFactoryAware,
ApplicationContextAware, InitializingBean, DisposableBean {
private String name;
// 阶段1:构造方法
public LifecycleBean() {
System.out.println("1. 构造方法: LifecycleBean实例化");
}
// 阶段2:属性注入
@Autowired
public void setName(String name) {
System.out.println("2. 属性注入: name = " + name);
this.name = name;
}
// 阶段3:BeanNameAware
@Override
public void setBeanName(String name) {
System.out.println("3. BeanNameAware: beanName = " + name);
}
// 阶段4:BeanFactoryAware
@Override
public void setBeanFactory(BeanFactory beanFactory) {
System.out.println("4. BeanFactoryAware: " + beanFactory);
}
// 阶段5:ApplicationContextAware
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
System.out.println("5. ApplicationContextAware: " + applicationContext);
}
// 阶段6:BeanPostProcessor前置处理
// (由容器自动调用,无需实现接口)
// 阶段7:InitializingBean
@Override
public void afterPropertiesSet() {
System.out.println("7. InitializingBean.afterPropertiesSet()");
}
// 阶段8:自定义初始化方法
@PostConstruct
public void init() {
System.out.println("8. @PostConstruct: 自定义初始化方法");
}
// 阶段9:BeanPostProcessor后置处理
// 业务方法
public void doWork() {
System.out.println("Bean正在执行业务方法");
}
// 阶段10:销毁
@PreDestroy
public void preDestroy() {
System.out.println("10. @PreDestroy: 销毁前");
}
@Override
public void destroy() {
System.out.println("11. DisposableBean.destroy()");
}
}
执行结果:
1. 构造方法: LifecycleBean实例化 2. 属性注入: name = testBean 3. BeanNameAware: beanName = lifecycleBean 4. BeanFactoryAware: org.springframework.beans.factory.support.DefaultListableBeanFactory@... 5. ApplicationContextAware: org.springframework.context.annotation.AnnotationConfigApplicationContext@... 6. BeanPostProcessor前置处理: postProcessBeforeInitialization 7. InitializingBean.afterPropertiesSet() 8. @PostConstruct: 自定义初始化方法 9. BeanPostProcessor后置处理: postProcessAfterInitialization Bean正在执行业务方法 10. @PreDestroy: 销毁前 11. DisposableBean.destroy()
阶段三:核心用法拆解——怎么用才能不出错?
3.1 依赖注入的详细用法
3.1.1 @Autowired注解详解
@Autowired是Spring中最常用的注解,它的行为受几个属性控制:
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER,
ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
/**
* 是否必须注入
* true:必须注入成功,否则容器启动失败
* false:可选注入,找不到依赖时设为null
*/
boolean required() default true;
}
场景一:required=false的使用
@Service
public class OptionalDependencyService {
@Autowired(required = false)
private MaybeExistDao maybeExistDao; // 如果容器中没有MaybeExistDao,则注入null
public void doSomething() {
if (maybeExistDao != null) {
maybeExistDao.save();
} else {
// 使用默认逻辑
System.out.println("使用默认存储");
}
}
}
3.1.2 处理多个实现类的歧义
当一个接口有多个实现类时,@Autowired会报错:expected single matching bean but found 2。有三种解决方案:
方案一:@Primary - 指定主要实现
public interface PaymentService {
void pay(BigDecimal amount);
}
@Service
@Primary // 当有多个实现时,优先使用这个
public class AlipayService implements PaymentService {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用支付宝支付: " + amount);
}
}
@Service
public class WechatPayService implements PaymentService {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用微信支付: " + amount);
}
}
@Service
public class OrderService {
@Autowired
private PaymentService paymentService; // 注入的是AlipayService
public void checkout(BigDecimal amount) {
paymentService.pay(amount);
}
}
方案二:@Qualifier - 指定名称
@Service
public class OrderService {
@Autowired
@Qualifier("wechatPayService") // 指定注入WechatPayService
private PaymentService paymentService;
// 也可以用在构造器参数上
public OrderService(@Qualifier("alipayService") PaymentService paymentService) {
this.paymentService = paymentService;
}
}
方案三:使用集合注入
@Service
public class PaymentManager {
@Autowired
private List<PaymentService> paymentServices; // 注入所有实现类
@Autowired
private Map<String, PaymentService> paymentServiceMap; // 注入beanName->实现
public void payWithAll(String type, BigDecimal amount) {
PaymentService service = paymentServiceMap.get(type + "PayService");
if (service != null) {
service.pay(amount);
} else {
throw new IllegalArgumentException("不支持的支付类型: " + type);
}
}
}
3.1.3 @Resource和@Inject的区别
除了@Autowired,Java规范还提供了另外两个注入注解:
@Service
public class DifferenceDemo {
// @Resource - JSR-250规范,默认按名称装配
@Resource(name = "userDao")
private UserDao userDao;
// @Resource也可以不指定name,默认按字段名装配
@Resource
private EmailService emailService;
// @Inject - JSR-330规范,需要额外引入javax.inject依赖
@Inject
private LogService logService;
// @Named配合@Inject使用,类似于@Qualifier
@Inject
@Named("specialService")
private SpecialService specialService;
}
三者的对比:
| 特性 | @Autowired | @Resource | @Inject |
|---|---|---|---|
| 来源 | Spring专属 | JSR-250(Java规范) | JSR-330(Java规范) |
| 装配方式 | 按类型 | 默认按名称 | 按类型 |
| required属性 | 支持 | 不支持 | 不支持 |
| 与@Qualifier配合 | 支持 | 支持(@Named) | 支持(@Named) |
3.1.4 循环依赖的解决方案
循环依赖是指A依赖B,B依赖A,形成闭环。Spring通过三级缓存解决这个问题。
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
public void sayHello() {
System.out.println("Hello from A");
serviceB.sayHello();
}
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
public void sayHello() {
System.out.println("Hello from B");
serviceA.sayHello();
}
}
Spring解决循环依赖的原理(三级缓存):
public class DefaultSingletonBeanRegistry {
// 一级缓存:单例对象的缓存,存放完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期单例对象的缓存,存放尚未完成初始化的Bean(半成品)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:单例工厂的缓存,存放ObjectFactory,用于创建Bean的代理对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
解决流程:
-
Spring创建ServiceA,发现依赖ServiceB,将ServiceA的半成品放入三级缓存
-
Spring创建ServiceB,发现依赖ServiceA,从三级缓存获取ServiceA的半成品注入
-
ServiceB初始化完成,放入一级缓存
-
ServiceA继续初始化,注入ServiceB,完成初始化
注意:构造器注入的循环依赖无法解决,因为构造器调用时Bean还未实例化,无法放入缓存。
3.2 AOP的高级用法
3.2.1 切入点表达式详解
切入点表达式是AOP的核心,决定了哪些方法会被拦截。Spring支持9种切入点指示符:
@Aspect
@Component
public class PointcutDemo {
// 1. execution - 最常用,匹配方法执行
@Pointcut("execution(public * com.example.service.*.*(..))")
public void serviceLayer() {}
// 2. within - 匹配指定类型内的方法
@Pointcut("within(com.example.service.UserService)")
public void userServiceOnly() {}
// 3. this - 匹配代理对象为指定类型的连接点
@Pointcut("this(com.example.service.UserService)")
public void proxyIsUserService() {}
// 4. target - 匹配目标对象为指定类型的连接点
@Pointcut("target(com.example.service.UserService)")
public void targetIsUserService() {}
// 5. args - 匹配参数类型
@Pointcut("args(Long, String)")
public void longAndStringArgs() {}
// 6. @target - 匹配目标对象有指定注解
@Pointcut("@target(org.springframework.stereotype.Service)")
public void serviceAnnotation() {}
// 7. @args - 匹配运行时参数有指定注解
@Pointcut("@args(com.example.Valid)")
public void validArgs() {}
// 8. @within - 匹配声明类型有指定注解
@Pointcut("@within(org.springframework.stereotype.Controller)")
public void controllerAnnotation() {}
// 9. @annotation - 匹配有指定注解的方法
@Pointcut("@annotation(com.example.Loggable)")
public void loggableMethods() {}
}
execution表达式的完整语法:
execution([权限修饰符] [返回值类型] [类全路径].[方法名]([参数类型]) [throws 异常])
常用示例:
// 匹配所有public方法
@Pointcut("execution(public * *(..))")
// 匹配所有以save开头的方法
@Pointcut("execution(* save*(..))")
// 匹配UserService中的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
// 匹配service包及其子包中的所有方法
@Pointcut("execution(* com.example.service..*.*(..))")
// 匹配只有一个Long参数的方法
@Pointcut("execution(* *.*(Long))")
// 匹配第一个参数是Long,后面任意的方法
@Pointcut("execution(* *.*(Long,..))")
// 匹配抛出IllegalArgumentException的方法
@Pointcut("execution(* *(..) throws IllegalArgumentException)")
3.2.2 多个切面的执行顺序
当一个方法被多个切面拦截时,可以通过@Order注解控制执行顺序:
@Aspect
@Order(1) // 数字越小,优先级越高,越先执行(前置通知)和越后执行(后置通知)
@Component
public class LoggingAspect {
@Before("serviceLayer()")
public void logBefore() {
System.out.println("日志切面 - 前置");
}
@After("serviceLayer()")
public void logAfter() {
System.out.println("日志切面 - 后置");
}
}
@Aspect
@Order(2)
@Component
public class TransactionAspect {
@Before("serviceLayer()")
public void beginTransaction() {
System.out.println("事务切面 - 开启事务");
}
@After("serviceLayer()")
public void commitTransaction() {
System.out.println("事务切面 - 提交事务");
}
}
// 执行顺序:
// 日志切面 - 前置
// 事务切面 - 开启事务
// [目标方法执行]
// 事务切面 - 提交事务
// 日志切面 - 后置
3.2.3 获取连接点信息
在通知方法中,可以通过JoinPoint获取方法执行的上下文信息:
@Aspect
@Component
public class JoinPointInfoAspect {
@Before("serviceLayer()")
public void getInfo(JoinPoint joinPoint) {
// 获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("方法名: " + signature.getName());
System.out.println("完整签名: " + signature);
// 获取目标对象
Object target = joinPoint.getTarget();
System.out.println("目标对象: " + target.getClass().getSimpleName());
// 获取代理对象
Object proxy = joinPoint.getThis();
System.out.println("代理对象: " + proxy.getClass().getSimpleName());
// 获取参数
Object[] args = joinPoint.getArgs();
System.out.println("参数个数: " + args.length);
for (int i = 0; i < args.length; i++) {
System.out.println("参数" + i + ": " + args[i]);
}
}
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void getReturnValue(JoinPoint joinPoint, Object result) {
System.out.println("返回值: " + result);
}
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void getException(JoinPoint joinPoint, Exception ex) {
System.out.println("异常信息: " + ex.getMessage());
}
}
3.2.4 使用AOP实现性能监控
一个实际的企业级应用场景:监控每个Service方法的执行时间:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MonitorPerformance {
// 是否记录慢查询的阈值(毫秒)
long slowThreshold() default 1000;
}
@Aspect
@Component
public class PerformanceMonitorAspect {
private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorAspect.class);
@Around("@annotation(monitor)")
public Object monitor(ProceedingJoinPoint joinPoint, MonitorPerformance monitor) throws Throwable {
long startTime = System.currentTimeMillis();
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
try {
// 执行目标方法
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - startTime;
// 记录执行时间
logger.info("{}.{} 执行耗时: {}ms", className, methodName, elapsedTime);
// 检查是否超过阈值
if (elapsedTime > monitor.slowThreshold()) {
logger.warn("{}.{} 执行缓慢,耗时: {}ms,超过阈值: {}ms",
className, methodName, elapsedTime, monitor.slowThreshold());
}
return result;
} catch (Exception e) {
logger.error("{}.{} 执行异常: {}", className, methodName, e.getMessage());
throw e;
}
}
}
// 使用示例
@Service
public class OrderService {
@MonitorPerformance(slowThreshold = 500)
public Order queryOrder(Long orderId) {
// 模拟耗时操作
Thread.sleep(100);
return new Order(orderId);
}
@MonitorPerformance
public List<Order> queryAllOrders() {
// 模拟耗时操作
Thread.sleep(2000);
return Arrays.asList(new Order(1L), new Order(2L));
}
}
3.3 事务管理高级用法
3.3.1 @Transactional注解详解
@Transactional是Spring声明式事务的核心,它有多个重要属性:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { // 1. 事务管理器名称(当有多个事务管理器时使用) @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; // 2. 传播行为(默认:REQUIRED) Propagation propagation() default Propagation.REQUIRED; // 3. 隔离级别(默认:数据库默认隔离级别) Isolation isolation() default Isolation.DEFAULT; // 4. 超时时间(秒,默认:-1 不超时) int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; // 5. 是否只读事务(默认:false) boolean readOnly() default false; // 6. 哪些异常会导致回滚(默认:运行时异常) Class<? extends Throwable>[] rollbackFor() default {}; // 7. 哪些异常不会导致回滚 Class<? extends Throwable>[] noRollbackFor() default {}; // 8. 异常类名方式 String[] rollbackForClassName() default {}; String[] noRollbackForClassName() default {}; }
3.3.2 事务传播行为(7种)
事务传播行为定义了方法被调用时,事务如何传播:
| 传播行为 | 含义 | 使用场景 |
|---|---|---|
| REQUIRED | 支持当前事务,如果没有则创建新事务 | 默认值,90%的场景使用 |
| SUPPORTS | 支持当前事务,如果没有则以非事务方式执行 | 查询方法 |
| MANDATORY | 强制要求存在事务,否则抛异常 | 必须在事务内执行的操作 |
| REQUIRES_NEW | 挂起当前事务,创建新事务 | 日志记录、审计 |
| NOT_SUPPORTED | 以非事务方式执行,挂起当前事务 | 发送邮件等不需要事务的操作 |
| NEVER | 以非事务方式执行,存在事务则抛异常 | 不允许在事务中执行的操作 |
| NESTED | 嵌套事务(JDBC Savepoint) | 部分回滚场景 |
代码示例:
@Service
public class PropagationDemo {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
public void requiredDemo() {
// 如果调用者没有事务,创建新事务
// 如果调用者有事务,加入该事务
userService.updateUser();
logService.log("更新用户"); // 和updateUser在同一事务
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewDemo() {
// 总是创建新事务,挂起当前事务(如果有)
userService.updateUser(); // 在新事务中执行
}
@Transactional(propagation = Propagation.NESTED)
public void nestedDemo() {
// 嵌套事务,使用Savepoint
// 内层事务回滚只回滚到Savepoint,不影响外层事务
userService.updateUser(); // 如果这里失败,只回滚到这里
logService.log("更新用户"); // 外层事务继续
}
}
3.3.3 事务隔离级别(5种)
隔离级别定义了并发事务之间的可见性:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| DEFAULT | - | - | - | 使用数据库默认隔离级别(MySQL:REPEATABLE_READ) |
| READ_UNCOMMITTED | ✅ | ✅ | ✅ | 性能最好,安全性最差 |
| READ_COMMITTED | ❌ | ✅ | ✅ | Oracle默认,防止脏读 |
| REPEATABLE_READ | ❌ | ❌ | ✅ | MySQL默认,防止脏读和不可重复读 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 完全串行化,性能最差 |
问题复现示例:
@Service
public class IsolationDemo {
@Autowired
private ProductRepository productRepository;
// 脏读演示:一个事务读取另一个未提交事务的数据
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Product dirtyReadDemo(Long id) {
Product product = productRepository.findById(id);
// 可能读到其他事务未提交的数据(这些数据可能回滚)
return product;
}
// 不可重复读演示:同一事务中两次读取同一数据,结果不一致
@Transactional(isolation = Isolation.READ_COMMITTED)
public void nonRepeatableReadDemo(Long id) {
Product p1 = productRepository.findById(id); // 第一次读取
// 其他事务修改并提交了该数据
Thread.sleep(5000);
Product p2 = productRepository.findById(id); // 第二次读取,数据变了
System.out.println("两次读取结果是否一致: " + p1.getPrice().equals(p2.getPrice()));
}
// 幻读演示:同一事务中两次范围查询,结果行数不一致
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void phantomReadDemo(BigDecimal price) {
List<Product> list1 = productRepository.findByPriceLessThan(price); // 第一次查询
// 其他事务插入了符合条件的新数据
Thread.sleep(5000);
List<Product> list2 = productRepository.findByPriceLessThan(price); // 第二次查询,多了新行
System.out.println("两次查询行数: " + list1.size() + " -> " + list2.size());
}
}
3.3.4 事务失效的12种场景(必看)
这是面试和工作中的高频考点,我总结了12种事务失效的场景:
场景1:非public方法
@Service
public class TransactionFailDemo {
@Transactional
private void privateMethod() {
// 事务不会生效,Spring只对public方法进行代理
jdbcTemplate.update("...");
}
}
场景2:自身调用(同一个类中的方法调用)
@Service
public class SelfInvocationDemo {
@Transactional
public void methodA() {
// 事务生效
jdbcTemplate.update("insert into table ...");
}
public void methodB() {
// 直接调用methodA,事务失效(没有经过代理对象)
methodA(); // 事务不生效
}
}
解决方案:
@Service
public class SelfInvocationDemo {
@Autowired
private SelfInvocationDemo self; // 注入自己
@Transactional
public void methodA() {
jdbcTemplate.update("insert into table ...");
}
public void methodB() {
// 通过代理对象调用
self.methodA(); // 事务生效
}
}
场景3:异常被捕获未抛出
@Transactional
public void methodWithCatch() {
try {
jdbcTemplate.update("update account set balance = balance - 100 where id = 1");
int i = 1 / 0; // 抛出异常
} catch (Exception e) {
// 捕获了异常但没有抛出,事务不会回滚
log.error("发生错误", e);
}
}
场景4:抛出检查异常(非RuntimeException)
@Transactional
public void methodWithCheckedException() throws Exception {
jdbcTemplate.update("update account set balance = balance - 100 where id = 1");
throw new Exception("检查异常"); // 默认不回滚
}
解决方案:
@Transactional(rollbackFor = Exception.class) // 指定所有异常都回滚
public void methodWithRollbackFor() throws Exception {
jdbcTemplate.update("update account set balance = balance - 100 where id = 1");
throw new Exception("检查异常"); // 现在会回滚
}
场景5:多线程调用
@Transactional
public void methodWithThread() {
jdbcTemplate.update("update account set balance = balance - 100 where id = 1");
new Thread(() -> {
// 新线程中的操作不在原事务范围内
jdbcTemplate.update("update account set balance = balance + 100 where id = 2");
}).start();
}
场景6:数据库引擎不支持事务
// MySQL的MyISAM引擎不支持事务 // 需要改用InnoDB引擎
场景7:事务传播行为设置错误
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodWithNoTx() {
// 以非事务方式执行
jdbcTemplate.update("..."); // 不会回滚
}
场景8:@Transactional注解在接口上
public interface UserService {
@Transactional // 不推荐,可能不生效
void saveUser();
}
推荐做法:把@Transactional放在实现类的方法上。
场景9:方法被final修饰
@Service
public class FinalMethodDemo {
@Transactional
public final void finalMethod() {
// final方法不能被重写,CGLIB代理无法增强
jdbcTemplate.update("...");
}
}
场景10:事务管理器配置错误
@Configuration
public class TxConfig {
@Bean
public PlatformTransactionManager transactionManager() {
// 配置错误,比如数据源不对
return new DataSourceTransactionManager(wrongDataSource());
}
}
场景11:数据库连接问题
// 数据库连接超时、连接池耗尽等,导致无法开启事务
场景12:类没有被Spring管理
public class NotSpringBean {
@Transactional // 没被Spring管理,注解无效
public void method() { }
}
3.4 常见坑点演示
3.4.1 @Autowired注入为null的5种情况
情况1:对象不是Spring管理的
public class NonSpringClass {
@Autowired
private UserDao userDao; // null,因为该类不是Bean
public void doSomething() {
userDao.save(); // NullPointerException
}
}
情况2:手动new对象
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void test() {
// 错误:手动创建的对象不受Spring管理
OrderService orderService = new OrderService();
orderService.process(); // 内部的@Autowired都是null
}
}
情况3:在静态字段上使用@Autowired
@Component
public class StaticFieldDemo {
@Autowired
private static UserDao userDao; // null,静态字段不参与注入
public static void staticMethod() {
userDao.save(); // NullPointerException
}
}
解决方案:
@Component
public class StaticFieldDemo {
private static UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
StaticFieldDemo.userDao = userDao; // 通过setter注入静态字段
}
}
情况4:在Filter/Listener中使用
public class MyFilter implements Filter {
@Autowired
private UserService userService; // null,Filter不由Spring管理
public void doFilter(...) {
userService.doSomething(); // NullPointerException
}
}
解决方案:
public class MyFilter implements Filter {
private UserService userService;
@Override
public void init(FilterConfig filterConfig) {
// 手动从Spring容器获取
ApplicationContext ctx = WebApplicationContextUtils
.getRequiredWebApplicationContext(filterConfig.getServletContext());
userService = ctx.getBean(UserService.class);
}
}
情况5:在子父容器冲突中
// Spring MVC中,Controller在子容器,Service在父容器 // 如果配置不当,Controller可能无法注入Service
3.4.2 AOP失效的3种情况
情况1:私有方法调用
@Service
public class AopFailDemo {
@Loggable
private void privateMethod() {
// AOP不会拦截私有方法
}
public void publicMethod() {
privateMethod(); // 直接调用,不会触发AOP
}
}
情况2:同一个类中的方法调用
@Service
public class AopFailDemo {
@Loggable
public void methodA() {
System.out.println("Method A");
}
public void methodB() {
methodA(); // 直接调用,不会触发AOP(没有经过代理)
}
}
情况3:final方法
@Service
public class AopFailDemo {
@Loggable
public final void finalMethod() {
// final方法不能被CGLIB代理覆盖,AOP失效
}
}
3.4.3 事务失效的完整示例
结合以上所有场景,写一个完整的测试类:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TransactionFailTest {
@Autowired
private TransactionFailService transactionFailService;
@Test
public void testAllFailScenarios() {
// 1. 非public方法
transactionFailService.callPrivateMethod(); // 事务失效
// 2. 自身调用
transactionFailService.callMethodAFromB(); // 事务失效
// 3. 捕获异常未抛出
transactionFailService.methodWithCatch(); // 不回滚
// 4. 抛出检查异常
try {
transactionFailService.methodWithCheckedException();
} catch (Exception e) {
// 不回滚
}
// 5. 多线程
transactionFailService.methodWithThread(); // 子线程不回滚
// 6. 传播行为错误
transactionFailService.methodWithNoTx(); // 不回滚
// 验证数据库状态
}
}
阶段四:场景融合——串联电商下单全流程
4.1 业务流程串联
现在让我们看看在经典的电商下单场景中,Spring的这些核心特性是如何无缝协作的。
4.1.1 完整的电商下单流程
业务流程描述:
-
用户在前端点击"下单"按钮
-
系统校验用户状态(是否登录、账户是否正常)
-
校验商品库存是否充足
-
扣减库存
-
创建订单(状态为待支付)
-
扣减用户余额(如果是余额支付)
-
发送消息到订单队列(异步处理后续流程)
-
记录操作日志
-
返回下单结果
4.1.2 技术架构图
┌─────────────────────────────────────────────────────────────┐
│ 用户请求(下单) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Controller层(OrderController) │
│ @RestController │
│ 接收HTTP请求,参数校验 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ AOP切面(横切关注点) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LogAspect:记录请求参数、响应结果、执行时间 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ AuthAspect:校验用户权限、接口防刷 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CacheAspect:缓存查询结果(商品信息、用户信息) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Service层(OrderService) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 用户校验(调用UserService) │ │
│ │ @Autowired UserService │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2. 商品校验(调用ProductService) │ │
│ │ @Autowired ProductService │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3. 核心业务(事务管理) │ │
│ │ @Transactional │ │
│ │ ├─ 扣减库存(ProductDao) │ │
│ │ ├─ 创建订单(OrderDao) │ │
│ │ └─ 扣减余额(AccountDao) │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 4. 异步处理(发送消息) │ │
│ │ @Async │ │
│ │ └─ 发送订单消息到RabbitMQ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 各DAO层(数据访问) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ UserDao(MyBatis):查询用户信息 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ProductDao(MyBatis):查询商品、扣减库存 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ OrderDao(MyBatis):插入订单记录 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ AccountDao(MyBatis):查询/更新账户余额 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
4.1.3 完整代码实现
OrderController.java - 控制层
@RestController
@RequestMapping("/api/orders")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
public Result<OrderVO> createOrder(@RequestBody @Valid CreateOrderRequest request) {
log.info("收到创建订单请求: {}", request);
try {
OrderVO orderVO = orderService.createOrder(request);
return Result.success(orderVO);
} catch (BusinessException e) {
log.error("创建订单业务异常: {}", e.getMessage());
return Result.error(e.getCode(), e.getMessage());
} catch (Exception e) {
log.error("创建订单系统异常", e);
return Result.error("系统繁忙,请稍后重试");
}
}
}
OrderService.java - 业务层
@Service
@Slf4j
public class OrderService {
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Autowired
private AccountService accountService;
@Autowired
private OrderDao orderDao;
@Autowired
private MessageService messageService;
/**
* 创建订单(核心业务方法)
*/
@Transactional(rollbackFor = Exception.class)
public OrderVO createOrder(CreateOrderRequest request) {
log.info("开始创建订单: userId={}, productId={}", request.getUserId(), request.getProductId());
// 1. 校验用户状态
User user = userService.getUserById(request.getUserId());
if (user == null) {
throw new BusinessException("用户不存在");
}
if (user.getStatus() != UserStatus.NORMAL) {
throw new BusinessException("用户状态异常");
}
// 2. 校验商品信息
Product product = productService.getProductById(request.getProductId());
if (product == null) {
throw new BusinessException("商品不存在");
}
if (product.getStock() < request.getQuantity()) {
throw new BusinessException("商品库存不足");
}
// 3. 计算订单金额
BigDecimal totalAmount = product.getPrice()
.multiply(BigDecimal.valueOf(request.getQuantity()));
// 4. 扣减库存(事务内)
boolean stockResult = productService.decreaseStock(
request.getProductId(), request.getQuantity());
if (!stockResult) {
throw new BusinessException("扣减库存失败");
}
// 5. 创建订单(事务内)
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setTotalAmount(totalAmount);
order.setStatus(OrderStatus.PENDING_PAYMENT);
order.setCreateTime(new Date());
int insertResult = orderDao.insert(order);
if (insertResult != 1) {
throw new BusinessException("创建订单失败");
}
// 6. 扣减余额(如果是余额支付)
if (request.getPayMethod() == PayMethod.BALANCE) {
boolean balanceResult = accountService.decreaseBalance(
request.getUserId(), totalAmount);
if (!balanceResult) {
throw new BusinessException("扣减余额失败");
}
}
// 7. 发送订单创建消息(异步)
messageService.sendOrderCreatedMessage(order);
log.info("订单创建成功: orderNo={}", order.getOrderNo());
// 8. 返回订单信息
return convertToVO(order, product, user);
}
private String generateOrderNo() {
return "ORD" + System.currentTimeMillis() +
String.format("%03d", new Random().nextInt(1000));
}
}
LogAspect.java - 日志切面
@Aspect
@Component
@Slf4j
public class LogAspect {
/**
* 环绕通知:记录Controller层所有方法的执行时间和参数
*/
@Around("execution(* com.example.controller.*.*(..))")
public Object logController(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
// 记录请求开始
log.info("【请求开始】{}.{},参数:{}", className, methodName,
args.length > 0 ? Arrays.toString(args) : "无参数");
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - startTime;
// 记录请求结束
log.info("【请求结束】{}.{},耗时:{}ms,响应:{}",
className, methodName, elapsedTime, result);
return result;
} catch (Exception e) {
long elapsedTime = System.currentTimeMillis() - startTime;
log.error("【请求异常】{}.{},耗时:{}ms,异常:{}",
className, methodName, elapsedTime, e.getMessage(), e);
throw e;
}
}
/**
* 后置通知:记录Service层的重要操作
*/
@AfterReturning(pointcut = "@annotation(com.example.annotation.LogOperation)",
returning = "result")
public void logOperation(JoinPoint joinPoint, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
LogOperation annotation = signature.getMethod().getAnnotation(LogOperation.class);
String operation = annotation.value();
String username = getCurrentUsername();
log.info("【操作日志】用户:{},操作:{},结果:{}",
username, operation, result);
// 可以异步保存到数据库
saveOperationLog(username, operation, result);
}
private String getCurrentUsername() {
// 从SecurityContext获取当前用户名
return SecurityContextHolder.getContext().getAuthentication().getName();
}
@Async
public void saveOperationLog(String username, String operation, Object result) {
// 保存到数据库
}
}
AuthAspect.java - 权限校验切面
@Aspect
@Component
public class AuthAspect {
/**
* 前置通知:校验用户权限
*/
@Before("@annotation(requirePermission)")
public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) {
// 获取当前用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
throw new BusinessException("用户未登录");
}
// 获取当前用户的权限
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
Set<String> userPermissions = getUserPermissions(userDetails.getUsername());
// 校验所需权限
String[] requiredPermissions = requirePermission.value();
for (String permission : requiredPermissions) {
if (!userPermissions.contains(permission)) {
log.warn("用户 {} 缺少权限: {}", userDetails.getUsername(), permission);
throw new BusinessException("权限不足");
}
}
log.debug("权限校验通过: {}", userDetails.getUsername());
}
/**
* 前置通知:接口防刷(限流)
*/
@Before("@annotation(rateLimit)")
public void rateLimit(JoinPoint joinPoint, RateLimit rateLimit) {
String key = getRateLimitKey(joinPoint);
int maxCount = rateLimit.maxCount();
long timeWindow = rateLimit.timeWindow();
// 使用Redis进行计数
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, timeWindow, TimeUnit.SECONDS);
}
if (count > maxCount) {
log.warn("接口限流: {}, 请求次数: {}", key, count);
throw new BusinessException("请求太频繁,请稍后重试");
}
}
private String getRateLimitKey(JoinPoint joinPoint) {
// 生成限流key:类名+方法名+用户ID
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
String userId = getCurrentUserId();
return "rate:limit:" + className + ":" + methodName + ":" + userId;
}
}
4.2 技术选型对比
在实际企业开发中,我们需要根据具体场景选择合适的实现方案。
4.2.1 IOC容器选型对比
| 特性 | XML配置 | 注解配置 | Java配置 |
|---|---|---|---|
| 配置方式 | XML文件 | @Component系列注解 | @Configuration + @Bean |
| 可读性 | 集中管理,容易查看 | 分散在类上 | 集中管理,类型安全 |
| 灵活性 | 高,无需重新编译 | 低,需要修改源码 | 中,需要重新编译 |
| 适用场景 | 第三方库配置 | 自己开发的组件 | 复杂配置、条件配置 |
企业推荐:混合使用。基础组件用注解,数据源、事务管理等用Java配置。
4.2.2 AOP实现选型对比
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 实现方式 | 动态代理(JDK或CGLIB) | 字节码织入(编译期、类加载期) |
| 性能 | 运行时开销 | 编译时增强,无运行时开销 |
| 支持连接点 | 方法级别 | 构造器、字段、方法等 |
| 使用复杂度 | 简单 | 复杂(需要特殊编译) |
| 适用场景 | 大部分业务场景 | 细粒度控制、性能极致要求 |
企业推荐:99%的场景使用Spring AOP即可,只有在对私有方法、静态方法进行拦截时才考虑AspectJ。
4.2.3 事务管理选型对比
| 特性 | 编程式事务 | 声明式事务(@Transactional) | TransactionTemplate |
|---|---|---|---|
| 代码侵入性 | 高 | 低 | 中 |
| 灵活性 | 最高 | 低 | 中 |
| 可读性 | 差 | 好 | 中 |
| 适用场景 | 复杂事务控制 | 标准业务 | 需要编程控制但不想太复杂 |
企业推荐:标准业务用@Transactional,复杂事务(如需要动态决定是否提交)用TransactionTemplate。
4.2.4 缓存选型对比
| 特性 | Spring Cache抽象 | Redis | Caffeine |
|---|---|---|---|
| 定位 | 缓存门面 | 分布式缓存 | 本地缓存 |
| 适用场景 | 统一缓存接口 | 多实例共享缓存 | 单实例高频访问 |
| 性能 | 中等 | 网络开销 | 极高 |
| 持久化 | 无 | 支持 | 不支持 |
企业推荐:Spring Cache + Redis(分布式) + Caffeine(本地二级缓存)组合使用。
阶段五:企业级实战——手写一个完整的电商项目
5.1 项目概述
我们将开发一个简化的商品管理系统,包含以下功能:
-
商品CRUD操作
-
商品缓存(Redis)
-
商品搜索(MyBatis动态SQL)
-
操作日志记录(AOP)
-
事务管理(下单扣库存)
5.2 项目初始化
5.2.1 技术栈
-
Spring Boot 2.7.x:项目基础框架
-
MyBatis-Plus:数据持久化
-
Redis:缓存
-
Lombok:简化代码
-
H2 Database:内存数据库(便于测试)
5.2.2 Maven依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
</parent>
<groupId>com.example</groupId>
<artifactId>product-manager</artifactId>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
<mybatis-plus.version>3.5.3</mybatis-plus.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Spring Boot Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- H2 Database(测试用) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
5.2.3 配置文件(application.yml)
spring:
application:
name: product-manager
# H2数据库配置
datasource:
url: jdbc:h2:mem:productdb;DB_CLOSE_DELAY=-1;MODE=MySQL
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
path: /h2-console
# Redis配置
redis:
host: localhost
port: 6379
database: 0
timeout: 5000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
# 缓存配置
cache:
type: redis
redis:
time-to-live: 600000 # 10分钟
# MyBatis-Plus配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
# 日志配置
logging:
level:
com.example: DEBUG
org.springframework: INFO
5.3 核心代码实现
5.3.1 实体类(Model)
Product.java - 商品实体
package com.example.model;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
@TableName("product")
public class Product {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String category;
private BigDecimal price;
private Integer stock;
private String description;
private String imageUrl;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@TableLogic
private Integer deleted;
}
Order.java - 订单实体
package com.example.model;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
@TableName("`order`") // order是MySQL关键字,需要转义
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long userId;
private Long productId;
private Integer quantity;
private BigDecimal totalAmount;
private Integer status; // 0-待支付 1-已支付 2-已取消
private Date createTime;
private Date payTime;
}
5.3.2 DAO层(Mapper)
ProductMapper.java
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.model.Product;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
/**
* 扣减库存(乐观锁实现)
*/
@Update("UPDATE product SET stock = stock - #{quantity}, " +
"version = version + 1 " +
"WHERE id = #{id} AND stock >= #{quantity} AND version = #{version}")
int decreaseStock(@Param("id") Long id,
@Param("quantity") Integer quantity,
@Param("version") Integer version);
/**
* 查询商品并加锁(悲观锁)
*/
@Select("SELECT * FROM product WHERE id = #{id} FOR UPDATE")
Product selectForUpdate(@Param("id") Long id);
}
OrderMapper.java
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.model.Order;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
/**
* 根据订单号查询
*/
@Select("SELECT * FROM `order` WHERE order_no = #{orderNo}")
Order selectByOrderNo(@Param("orderNo") String orderNo);
}
5.3.3 Service层
ProductService.java
package com.example.service;
import com.example.dto.ProductDTO;
import com.example.dto.ProductQueryDTO;
import com.example.model.Product;
import java.util.List;
public interface ProductService {
Product getProductById(Long id);
Product getProductWithCache(Long id);
List<Product> listProducts(ProductQueryDTO query);
boolean createProduct(ProductDTO productDTO);
boolean updateProduct(ProductDTO productDTO);
boolean deleteProduct(Long id);
boolean decreaseStock(Long id, Integer quantity);
}
ProductServiceImpl.java
package com.example.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.dto.ProductDTO;
import com.example.dto.ProductQueryDTO;
import com.example.exception.BusinessException;
import com.example.mapper.ProductMapper;
import com.example.model.Product;
import com.example.service.ProductService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
@RequiredArgsConstructor
public class ProductServiceImpl implements ProductService {
private final ProductMapper productMapper;
private final RedisTemplate<String, Object> redisTemplate;
private static final String PRODUCT_CACHE_KEY = "product:";
private static final String PRODUCT_LIST_CACHE_KEY = "product:list:";
@Override
public Product getProductById(Long id) {
log.debug("从数据库查询商品,ID: {}", id);
return productMapper.selectById(id);
}
@Override
@Cacheable(value = "product", key = "#id", unless = "#result == null")
public Product getProductWithCache(Long id) {
log.info("缓存未命中,从数据库查询商品,ID: {}", id);
return productMapper.selectById(id);
}
@Override
public List<Product> listProducts(ProductQueryDTO query) {
log.info("分页查询商品: {}", query);
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
// 动态拼接查询条件
if (StringUtils.hasText(query.getName())) {
wrapper.like(Product::getName, query.getName());
}
if (StringUtils.hasText(query.getCategory())) {
wrapper.eq(Product::getCategory, query.getCategory());
}
if (query.getMinPrice() != null) {
wrapper.ge(Product::getPrice, query.getMinPrice());
}
if (query.getMaxPrice() != null) {
wrapper.le(Product::getPrice, query.getMaxPrice());
}
// 按创建时间倒序
wrapper.orderByDesc(Product::getCreateTime);
// 分页查询
Page<Product> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<Product> pageResult = productMapper.selectPage(page, wrapper);
return pageResult.getRecords();
}
@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = "product", key = "#productDTO.id")
public boolean createProduct(ProductDTO productDTO) {
log.info("创建商品: {}", productDTO);
// 参数校验
if (productDTO.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException("商品价格必须大于0");
}
if (productDTO.getStock() < 0) {
throw new BusinessException("商品库存不能为负数");
}
// DTO转实体
Product product = new Product();
product.setName(productDTO.getName());
product.setCategory(productDTO.getCategory());
product.setPrice(productDTO.getPrice());
product.setStock(productDTO.getStock());
product.setDescription(productDTO.getDescription());
product.setImageUrl(productDTO.getImageUrl());
int result = productMapper.insert(product);
if (result > 0) {
log.info("商品创建成功,ID: {}", product.getId());
// 清除列表缓存
redisTemplate.delete(PRODUCT_LIST_CACHE_KEY + "*");
return true;
}
return false;
}
@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = "product", key = "#productDTO.id")
public boolean updateProduct(ProductDTO productDTO) {
log.info("更新商品: {}", productDTO);
// 检查商品是否存在
Product existing = productMapper.selectById(productDTO.getId());
if (existing == null) {
throw new BusinessException("商品不存在");
}
// 更新字段
Product product = new Product();
product.setId(productDTO.getId());
product.setName(productDTO.getName());
product.setCategory(productDTO.getCategory());
product.setPrice(productDTO.getPrice());
product.setDescription(productDTO.getDescription());
product.setImageUrl(productDTO.getImageUrl());
int result = productMapper.updateById(product);
if (result > 0) {
log.info("商品更新成功,ID: {}", product.getId());
// 清除列表缓存
redisTemplate.delete(PRODUCT_LIST_CACHE_KEY + "*");
return true;
}
return false;
}
@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = "product", key = "#id")
public boolean deleteProduct(Long id) {
log.info("删除商品,ID: {}", id);
int result = productMapper.deleteById(id);
if (result > 0) {
log.info("商品删除成功,ID: {}", id);
// 清除列表缓存
redisTemplate.delete(PRODUCT_LIST_CACHE_KEY + "*");
return true;
}
return false;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean decreaseStock(Long id, Integer quantity) {
log.info("扣减库存,商品ID: {}, 数量: {}", id, quantity);
// 查询商品(带版本号)
Product product = productMapper.selectById(id);
if (product == null) {
throw new BusinessException("商品不存在");
}
if (product.getStock() < quantity) {
throw new BusinessException("库存不足");
}
// 乐观锁扣减库存
int result = productMapper.decreaseStock(id, quantity, product.getVersion());
if (result > 0) {
log.info("扣减库存成功,商品ID: {}", id);
// 清除缓存
redisTemplate.delete(PRODUCT_CACHE_KEY + id);
return true;
} else {
log.warn("扣减库存失败,商品ID: {},可能并发更新", id);
throw new BusinessException("扣减库存失败,请重试");
}
}
}
5.3.4 Controller层
ProductController.java
package com.example.controller;
import com.example.dto.ProductDTO;
import com.example.dto.ProductQueryDTO;
import com.example.model.Product;
import com.example.service.ProductService;
import com.example.vo.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
@Validated
public class ProductController {
private final ProductService productService;
/**
* 查询单个商品(带缓存)
*/
@GetMapping("/{id}")
public Result<Product> getProduct(@PathVariable Long id) {
log.info("查询商品,ID: {}", id);
Product product = productService.getProductWithCache(id);
if (product == null) {
return Result.error("商品不存在");
}
return Result.success(product);
}
/**
* 分页查询商品列表
*/
@GetMapping("/list")
public Result<List<Product>> listProducts(ProductQueryDTO query) {
log.info("分页查询商品: {}", query);
List<Product> products = productService.listProducts(query);
return Result.success(products);
}
/**
* 创建商品
*/
@PostMapping
public Result<Long> createProduct(@Valid @RequestBody ProductDTO productDTO) {
log.info("创建商品: {}", productDTO);
boolean success = productService.createProduct(productDTO);
if (success) {
return Result.success("创建成功", productDTO.getId());
} else {
return Result.error("创建失败");
}
}
/**
* 更新商品
*/
@PutMapping("/{id}")
public Result<Void> updateProduct(@PathVariable Long id,
@Valid @RequestBody ProductDTO productDTO) {
log.info("更新商品,ID: {}", id);
productDTO.setId(id);
boolean success = productService.updateProduct(productDTO);
if (success) {
return Result.success("更新成功");
} else {
return Result.error("更新失败");
}
}
/**
* 删除商品
*/
@DeleteMapping("/{id}")
public Result<Void> deleteProduct(@PathVariable Long id) {
log.info("删除商品,ID: {}", id);
boolean success = productService.deleteProduct(id);
if (success) {
return Result.success("删除成功");
} else {
return Result.error("删除失败");
}
}
/**
* 扣减库存
*/
@PostMapping("/{id}/decrease-stock")
public Result<Void> decreaseStock(@PathVariable Long id,
@RequestParam Integer quantity) {
log.info("扣减库存,商品ID: {}, 数量: {}", id, quantity);
boolean success = productService.decreaseStock(id, quantity);
if (success) {
return Result.success("扣减成功");
} else {
return Result.error("扣减失败");
}
}
}
5.3.5 AOP切面
LogAspect.java - 日志切面
package com.example.aspect;
import com.example.annotation.LogOperation;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {
private final ObjectMapper objectMapper;
/**
* 切点:所有Controller方法
*/
@Pointcut("execution(* com.example.controller.*.*(..))")
public void controllerPointcut() {}
/**
* 切点:有@LogOperation注解的方法
*/
@Pointcut("@annotation(com.example.annotation.LogOperation)")
public void logOperationPointcut() {}
/**
* 环绕通知:记录Controller执行情况
*/
@Around("controllerPointcut()")
public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 获取请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取方法信息
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
// 记录请求日志
log.info("【请求开始】{}.{} | IP: {} | URL: {} | 参数: {}",
className, methodName,
request.getRemoteAddr(),
request.getRequestURI(),
args.length > 0 ? Arrays.toString(args) : "无参数");
try {
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - startTime;
// 记录响应日志
log.info("【请求结束】{}.{} | 耗时: {}ms | 响应: {}",
className, methodName, elapsedTime,
objectMapper.writeValueAsString(result));
return result;
} catch (Exception e) {
long elapsedTime = System.currentTimeMillis() - startTime;
log.error("【请求异常】{}.{} | 耗时: {}ms | 异常: {}",
className, methodName, elapsedTime, e.getMessage(), e);
throw e;
}
}
/**
* 环绕通知:记录操作日志
*/
@Around("logOperationPointcut()")
public Object aroundLogOperation(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
LogOperation annotation = signature.getMethod().getAnnotation(LogOperation.class);
String operation = annotation.value();
// 获取当前用户
String username = getCurrentUsername();
log.info("【操作日志】用户: {} | 操作: {} | 开始执行", username, operation);
try {
Object result = joinPoint.proceed();
log.info("【操作日志】用户: {} | 操作: {} | 执行成功", username, operation);
// 异步保存到数据库
saveOperationLog(username, operation, true, null);
return result;
} catch (Exception e) {
log.error("【操作日志】用户: {} | 操作: {} | 执行失败: {}",
username, operation, e.getMessage());
saveOperationLog(username, operation, false, e.getMessage());
throw e;
}
}
private String getCurrentUsername() {
// 实际项目从SecurityContext获取
return "admin";
}
private void saveOperationLog(String username, String operation,
boolean success, String errorMsg) {
// 实际项目异步保存到数据库
log.debug("保存操作日志到数据库: {} | {} | {} | {}",
username, operation, success, errorMsg);
}
}
PerformanceAspect.java - 性能监控切面
package com.example.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Slf4j
@Aspect
@Component
public class PerformanceAspect {
// 方法调用次数统计
private final ConcurrentHashMap<String, AtomicLong> callCountMap = new ConcurrentHashMap<>();
// 方法总耗时统计
private final ConcurrentHashMap<String, AtomicLong> totalTimeMap = new ConcurrentHashMap<>();
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {}
@Around("servicePointcut()")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
String key = className + "." + methodName;
long startTime = System.nanoTime();
try {
Object result = joinPoint.proceed();
return result;
} finally {
long elapsedTime = System.nanoTime() - startTime;
long elapsedMs = elapsedTime / 1_000_000; // 转换为毫秒
// 更新统计
callCountMap.computeIfAbsent(key, k -> new AtomicLong()).incrementAndGet();
totalTimeMap.computeIfAbsent(key, k -> new AtomicLong()).addAndGet(elapsedMs);
// 记录慢查询
if (elapsedMs > 1000) {
log.warn("【慢查询】{} 执行耗时: {}ms", key, elapsedMs);
}
// 每100次调用输出一次统计
long count = callCountMap.get(key).get();
if (count % 100 == 0) {
long totalTime = totalTimeMap.get(key).get();
double avgTime = (double) totalTime / count;
log.info("【性能统计】{} | 调用次数: {} | 总耗时: {}ms | 平均耗时: {:.2f}ms",
key, count, totalTime, avgTime);
}
}
}
}
5.3.6 配置类
RedisConfig.java
package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer序列化value
Jackson2JsonRedisSerializer<Object> jacksonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
jacksonSerializer.setObjectMapper(objectMapper);
// key使用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// value使用Jackson2JsonRedisSerializer
template.setValueSerializer(jacksonSerializer);
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // 缓存10分钟
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new Jackson2JsonRedisSerializer<>(Object.class)))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
MyBatisPlusConfig.jav
package com.example.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
/**
* MyBatis-Plus插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
5.3.7 异常处理
GlobalExceptionHandler.java - 全局异常处理器
package com.example.handler;
import com.example.exception.BusinessException;
import com.example.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK)
public Result<Void> handleBusinessException(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
/**
* 处理参数校验异常(@RequestBody)
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.warn("参数校验失败: {}", message);
return Result.error("400", message);
}
/**
* 处理参数校验异常(@RequestParam)
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<Void> handleConstraintViolationException(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
log.warn("参数校验失败: {}", message);
return Result.error("400", message);
}
/**
* 处理其他未知异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.error("500", "系统繁忙,请稍后重试");
}
}
5.4 单元测试
ProductServiceTest.java
package com.example.service;
import com.example.dto.ProductDTO;
import com.example.exception.BusinessException;
import com.example.model.Product;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
@SpringBootTest
public class ProductServiceTest {
@Autowired
private ProductService productService;
@Test
public void testCreateProduct() {
// 准备数据
ProductDTO dto = new ProductDTO();
dto.setName("测试商品");
dto.setCategory("电子产品");
dto.setPrice(new BigDecimal("99.99"));
dto.setStock(100);
dto.setDescription("这是一个测试商品");
// 执行
boolean result = productService.createProduct(dto);
// 验证
Assertions.assertTrue(result);
Assertions.assertNotNull(dto.getId());
}
@Test
public void testCreateProductWithNegativePrice() {
// 准备数据(价格负数)
ProductDTO dto = new ProductDTO();
dto.setName("测试商品");
dto.setPrice(new BigDecimal("-10"));
dto.setStock(100);
// 执行并验证异常
Assertions.assertThrows(BusinessException.class, () -> {
productService.createProduct(dto);
});
}
@Test
public void testGetProductWithCache() {
// 先创建商品
ProductDTO dto = new ProductDTO();
dto.setName("缓存测试商品");
dto.setPrice(new BigDecimal("100"));
dto.setStock(50);
productService.createProduct(dto);
// 第一次查询(缓存未命中)
long start1 = System.currentTimeMillis();
Product product1 = productService.getProductWithCache(dto.getId());
long time1 = System.currentTimeMillis() - start1;
// 第二次查询(缓存命中)
long start2 = System.currentTimeMillis();
Product product2 = productService.getProductWithCache(dto.getId());
long time2 = System.currentTimeMillis() - start2;
// 验证
Assertions.assertNotNull(product1);
Assertions.assertNotNull(product2);
Assertions.assertEquals(product1.getId(), product2.getId());
Assertions.assertTrue(time2 < time1, "缓存查询应该比数据库查询快");
}
@Test
@org.springframework.transaction.annotation.Transactional
public void testDecreaseStock() {
// 准备数据
ProductDTO dto = new ProductDTO();
dto.setName("库存测试商品");
dto.setPrice(new BigDecimal("50"));
dto.setStock(10);
productService.createProduct(dto);
// 扣减库存
boolean result = productService.decreaseStock(dto.getId(), 3);
// 验证
Assertions.assertTrue(result);
// 查询最新库存
Product product = productService.getProductById(dto.getId());
Assertions.assertEquals(7, product.getStock()); // 10 - 3 = 7
}
}
5.5 项目部署
5.5.1 Maven打包
# 清理并打包 mvn clean package # 跳过测试打包 mvn clean package -DskipTests
5.5.2 Docker部署
Dockerfile
FROM openjdk:8-jre-alpine
# 设置时区
RUN apk add --no-cache tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
# 创建应用目录
RUN mkdir -p /app
WORKDIR /app
# 复制jar包
COPY target/product-manager-1.0.0.jar app.jar
# 暴露端口
EXPOSE 8080
# 启动命令
ENTRYPOINT ["java", "-jar", "app.jar"]
docker-compose.yml
version: '3.8'
services:
redis:
image: redis:6-alpine
container_name: product-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
networks:
- product-network
app:
build: .
container_name: product-app
ports:
- "8080:8080"
depends_on:
- redis
environment:
- SPRING_REDIS_HOST=redis
- SPRING_REDIS_PORT=6379
- SPRING_DATASOURCE_URL=jdbc:h2:mem:productdb;MODE=MySQL
networks:
- product-network
volumes:
redis-data:
networks:
product-network:
driver: bridge
启动命令
# 构建镜像 docker-compose build # 启动服务 docker-compose up -d # 查看日志 docker-compose logs -f app # 停止服务 docker-compose down
阶段六:复盘升华——从“会用”到“用好”
6.1 最佳实践总结
6.1.1 IOC最佳实践
1. 优先使用构造器注入
@Service
public class UserService {
private final UserDao userDao;
private final EmailService emailService;
// 构造器注入:依赖不可变,便于测试
public UserService(UserDao userDao, EmailService emailService) {
this.userDao = userDao;
this.emailService = emailService;
}
}
2. 使用@Primary解决多实现歧义
@Primary
@Service
public class DefaultPaymentService implements PaymentService {
// 默认实现
}
@Service
@Qualifier("vip")
public class VipPaymentService implements PaymentService {
// VIP专属实现
}
3. 合理使用作用域
@Component
@Scope("prototype") // 每次获取都创建新实例
public class TaskProcessor {
// 有状态的组件
}
@Component
@Scope("request") // 每个HTTP请求一个实例
public class RequestContext {
// 存储请求上下文信息
}
4. 避免字段注入带来的循环依赖隐患
// 不推荐
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB; // 可能导致循环依赖不易察觉
}
// 推荐
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB; // 构造器循环依赖会在启动时报错
}
}
6.1.2 AOP最佳实践
1. 切点表达式尽量精确
// 不推荐(太宽泛)
@Pointcut("execution(* *..*.*(..))")
// 推荐(精确到具体包)
@Pointcut("execution(* com.example.service.*.*(..))")
2. 优先使用@Around处理复杂逻辑
@Around("@annotation(Cacheable)")
public Object cache(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 查缓存
// 2. 缓存命中则直接返回
// 3. 缓存未命中则执行方法
// 4. 结果存入缓存
// 5. 返回结果
}
3. 注意代理方式的选择
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用CGLIB代理
public class AopConfig {
// 如果业务类没有实现接口,Spring会自动使用CGLIB
// 如果希望总是使用CGLIB,可以配置proxyTargetClass=true
}
4. 避免在同一个类中调用带切面的方法
@Service
public class UserService {
@Cacheable
public User getUser(Long id) { ... }
public List<User> getUsers() {
// 错误:直接调用,缓存失效
// User user = getUser(1L);
// 正确:通过代理对象调用
User user = ((UserService) AopContext.currentProxy()).getUser(1L);
return Collections.singletonList(user);
}
}
6.1.3 事务最佳实践
1. 事务放在Service层,而不是Controller或DAO层
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 多个DAO操作放在一个事务中
orderDao.insert(order);
productDao.decreaseStock(order.getProductId(), order.getQuantity());
accountDao.decreaseBalance(order.getUserId(), order.getTotalAmount());
}
}
2. 合理设置事务传播行为
@Service
public class OrderService {
// 核心业务:必须有事务
@Transactional(propagation = Propagation.REQUIRED)
public void processOrder() { ... }
// 查询操作:可以没有事务
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Order getOrder(Long id) { ... }
// 日志记录:独立事务,不受主事务影响
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(Log log) { ... }
}
3. 指定合理的隔离级别
// 统计报表:允许不可重复读,但要求性能
@Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
public Report generateReport() { ... }
// 财务操作:必须避免幻读
@Transactional(isolation = Isolation.SERIALIZABLE)
public void transferMoney() { ... }
4. 设置合适的超时时间
@Transactional(timeout = 10) // 10秒超时
public void batchProcess() {
// 批量处理,防止长时间占用连接
}
5. 使用rollbackFor指定回滚异常
@Transactional(rollbackFor = Exception.class) // 所有异常都回滚
public void method() throws Exception {
// 业务逻辑
}
6.1.4 缓存最佳实践
1. 设置合理的过期时间
@Cacheable(value = "product", key = "#id", unless = "#result == null")
public Product getProduct(Long id) {
// 商品信息缓存10分钟
return productMapper.selectById(id);
}
// 配置全局过期时间
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 默认30分钟
.serializeValuesWith(...);
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
2. 缓存更新策略
@Service
public class ProductService {
// 查询时缓存
@Cacheable(value = "product", key = "#id")
public Product getProduct(Long id) { ... }
// 更新时清除缓存
@CacheEvict(value = "product", key = "#product.id")
@Transactional
public void updateProduct(Product product) { ... }
// 删除时清除缓存
@CacheEvict(value = "product", key = "#id")
@Transactional
public void deleteProduct(Long id) { ... }
// 批量操作后清除所有商品缓存
@CacheEvict(value = "product", allEntries = true)
public void refreshAllProducts() { ... }
}
3. 避免缓存穿透
@Cacheable(value = "product", key = "#id", unless = "#result == null")
public Product getProduct(Long id) {
Product product = productMapper.selectById(id);
// 即使查询结果为null,也缓存一个空对象(设置较短过期时间)
if (product == null) {
redisTemplate.opsForValue().set("product:null:" + id,
new NullProduct(),
5, TimeUnit.MINUTES);
}
return product;
}
4. 避免缓存雪崩
// 设置随机过期时间,防止大量缓存同时失效
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(300 + new Random().nextInt(60))) // 300-360秒随机
.serializeValuesWith(...);
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
6.2 源码分析
6.2.1 IOC容器源码分析
核心类关系图:
BeanFactory (接口)
↑
├── HierarchicalBeanFactory (接口)
│ ↑
│ └── ApplicationContext (接口)
│ ↑
│ ├── ConfigurableApplicationContext (接口)
│ │ ↑
│ │ └── AbstractApplicationContext (抽象类)
│ │ ↑
│ │ └── GenericApplicationContext (类)
│ │ ↑
│ │ └── AnnotationConfigApplicationContext (类)
│ │
│ └── WebApplicationContext (接口)
│ ↑
│ └── AbstractRefreshableWebApplicationContext (类)
│
└── ListableBeanFactory (接口)
Bean的创建流程(源码简化):
// AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 实例化Bean(调用构造方法)
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
// 2. 提前暴露Bean(解决循环依赖)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// 3. 属性注入
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化Bean(调用init方法、Aware接口、BeanPostProcessor)
exposedObject = initializeBean(beanName, exposedObject, mbd);
return exposedObject;
}
三级缓存源码:
// DefaultSingletonBeanRegistry.java
public class DefaultSingletonBeanRegistry {
// 一级缓存:完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期暴露的Bean(半成品)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:Bean工厂,用于生成代理对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 先从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 再从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 最后从三级缓存获取
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
}
6.2.2 AOP源码分析
代理创建流程:
// AbstractAutoProxyCreator.java
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean != null) {
// 1. 获取适用于当前Bean的Advisor(切面)
Object[] advisors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
// 2. 创建代理
if (advisors != null) {
bean = createProxy(bean.getClass(), beanName, advisors, bean);
}
}
return bean;
}
protected Object createProxy(Class<?> beanClass, String beanName,
Object[] specificInterceptors, Object target) {
ProxyFactory proxyFactory = new ProxyFactory();
// 设置代理目标
proxyFactory.setTarget(target);
// 添加切面
for (Object advisor : specificInterceptors) {
proxyFactory.addAdvisor((Advisor) advisor);
}
// 创建代理(JDK或CGLIB)
return proxyFactory.getProxy(getProxyClassLoader());
}
JDK动态代理实现:
// JdkDynamicAopProxy.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
// 没有切面,直接调用目标方法
return method.invoke(target, args);
} else {
// 创建方法调用
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 执行拦截器链
return invocation.proceed();
}
}
6.2.3 事务源码分析
事务拦截器:
// TransactionInterceptor.java
public Object invoke(MethodInvocation invocation) throws Throwable {
// 1. 获取事务属性
TransactionAttribute txAttr = getTransactionAttributeSource()
.getTransactionAttribute(method, targetClass);
// 2. 确定事务管理器
PlatformTransactionManager tm = determineTransactionManager(txAttr);
// 3. 创建事务(根据传播行为)
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 4. 执行目标方法
retVal = invocation.proceed();
} catch (Throwable ex) {
// 5. 异常时回滚事务
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
// 6. 清理事务信息
cleanupTransactionInfo(txInfo);
}
// 7. 正常提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
6.3 技术演进
6.3.1 Spring版本的演进
| 版本 | 核心特性 | 企业应用状态 |
|---|---|---|
| Spring 1.x | XML配置、基础IOC/AOP | 历史项目 |
| Spring 2.x | 注解支持、AspectJ集成 | 维护中 |
| Spring 3.x | Java配置、REST支持 | 稳定运行 |
| Spring 4.x | Java 8支持、WebSocket | 主流 |
| Spring 5.x | 响应式编程、WebFlux | 新项目首选 |
6.3.2 配置方式的演进
// 1. XML时代(Spring 1.x)
// applicationContext.xml
<beans>
<bean id="userDao" class="com.example.UserDao"/>
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
</bean>
</beans>
// 2. 注解+XML时代(Spring 2.5)
<context:component-scan base-package="com.example"/>
// 3. Java配置时代(Spring 3.0)
@Configuration
@ComponentScan("com.example")
public class AppConfig { }
// 4. Spring Boot时代(Spring 4.0+)
@SpringBootApplication
public class Application { }
6.3.3 数据访问的演进
// 1. JDBC时代
Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("select * from user");
ResultSet rs = ps.executeQuery();
while(rs.next()) {
User user = new User();
user.setId(rs.getLong("id"));
// ...
}
// 2. Spring JDBC Template
jdbcTemplate.query("select * from user", new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
return user;
}
});
// 3. JPA
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name);
}
// 4. MyBatis-Plus
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 继承BaseMapper后,基本的CRUD都有了
}
6.3.4 异步编程的演进
// 1. Thread方式
new Thread(() -> {
// 异步任务
}).start();
// 2. ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> {
// 异步任务
});
// 3. @Async注解
@Async
public CompletableFuture<String> asyncMethod() {
return CompletableFuture.completedFuture("result");
}
// 4. CompletableFuture
CompletableFuture.supplyAsync(() -> {
return "result";
}).thenApply(result -> {
return result + " processed";
});
// 5. Reactive Streams (Spring WebFlux)
Mono<String> mono = Mono.fromCallable(() -> "result")
.map(r -> r + " processed");
// 6. Virtual Thread (Java 21+)
Thread.startVirtualThread(() -> {
// 虚拟线程,极轻量
});
6.4 面试/工作高频问题
6.4.1 IOC相关面试题
Q1:Spring Bean的生命周期是怎样的?
A:Spring Bean的生命周期可以分为以下几个阶段:
-
实例化:通过反射创建Bean实例
-
属性赋值:填充Bean的属性(依赖注入)
-
Aware接口回调:调用BeanNameAware、BeanFactoryAware等
-
BeanPostProcessor前置处理:调用postProcessBeforeInitialization
-
初始化:调用InitializingBean.afterPropertiesSet()和自定义init方法
-
BeanPostProcessor后置处理:调用postProcessAfterInitialization
-
使用中:Bean准备就绪,等待使用
-
销毁:容器关闭时,调用DisposableBean.destroy()和自定义destroy方法
Q2:Spring如何解决循环依赖?
A:Spring通过三级缓存解决单例Bean的循环依赖问题:
-
一级缓存:singletonObjects,存放完全初始化好的Bean
-
二级缓存:earlySingletonObjects,存放早期暴露的Bean(半成品)
-
三级缓存:singletonFactories,存放Bean工厂,用于生成代理对象
当A依赖B,B依赖A时:
-
A创建过程中,提前暴露自己到三级缓存
-
A依赖B,去创建B
-
B创建过程中,发现依赖A,从三级缓存获取A的半成品
-
B完成创建,放入一级缓存
-
A继续创建,完成初始化
Q3:@Autowired和@Resource的区别?
A:主要区别如下:
| 维度 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring专属 | JSR-250规范 |
| 装配方式 | 按类型(byType) | 默认按名称(byName) |
| required属性 | 支持 | 不支持 |
| 配合使用 | @Qualifier指定名称 | name属性指定名称 |
6.4.2 AOP相关面试题
Q4:Spring AOP和AspectJ的区别?
A:主要区别如下:
| 维度 | Spring AOP | AspectJ |
|---|---|---|
| 实现方式 | 动态代理(JDK或CGLIB) | 字节码织入(编译期、类加载期) |
| 性能 | 运行时开销 | 无运行时开销 |
| 连接点支持 | 仅方法级别 | 构造器、字段、方法等 |
| 使用复杂度 | 简单 | 复杂 |
| 适用场景 | 大部分业务场景 | 细粒度控制、性能极致要求 |
Q5:JDK动态代理和CGLIB的区别?
A:主要区别如下:
| 维度 | JDK动态代理 | CGLIB |
|---|---|---|
| 原理 | 基于接口生成代理类 | 基于继承生成子类 |
| 要求 | 目标类必须实现接口 | 目标类不能是final |
| 性能 | 反射调用,性能适中 | 生成字节码,性能较好 |
| 使用场景 | 有接口的类 | 无接口的类 |
Q6:@Transactional注解在什么情况下会失效?
A:12种失效场景:
-
非public方法
-
同一个类中的方法调用(自身调用)
-
异常被捕获未抛出
-
抛出检查异常(非RuntimeException)
-
多线程调用
-
数据库引擎不支持事务(如MyISAM)
-
传播行为设置错误(如NOT_SUPPORTED)
-
注解在接口上(不推荐)
-
方法被final修饰
-
事务管理器配置错误
-
数据库连接问题
-
类没有被Spring管理
6.4.3 事务相关面试题
Q7:Spring事务的传播行为有哪些?
A:7种传播行为:
-
REQUIRED:支持当前事务,没有则新建(默认)
-
SUPPORTS:支持当前事务,没有则以非事务方式执行
-
MANDATORY:强制要求存在事务,否则抛异常
-
REQUIRES_NEW:挂起当前事务,创建新事务
-
NOT_SUPPORTED:以非事务方式执行,挂起当前事务
-
NEVER:以非事务方式执行,存在事务则抛异常
-
NESTED:嵌套事务(JDBC Savepoint)
Q8:Spring事务的隔离级别有哪些?
A:5种隔离级别:
| 级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| DEFAULT | - | - | - | 数据库默认 |
| READ_UNCOMMITTED | ✅ | ✅ | ✅ | 读未提交 |
| READ_COMMITTED | ❌ | ✅ | ✅ | 读已提交 |
| REPEATABLE_READ | ❌ | ❌ | ✅ | 可重复读 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 串行化 |
Q9:什么是分布式事务?Spring如何支持?
A:分布式事务涉及多个数据库或服务的事务一致性。Spring支持的方式:
-
JTA(Java Transaction API):使用Atomikos等实现
-
TCC(Try-Confirm-Cancel):手动实现补偿机制
-
可靠消息最终一致性:RabbitMQ + 本地消息表
-
Seata:阿里开源的分布式事务框架
-
SAGA:长事务解决方案
6.4.4 综合面试题
Q10:Spring Boot和Spring的区别?
A:Spring Boot是在Spring基础上构建的:
| 维度 | Spring | Spring Boot |
|---|---|---|
| 定位 | 框架 | 框架的框架(脚手架) |
| 配置 | 手动配置 | 自动配置 |
| 启动方式 | 手动创建容器 | 直接运行main方法 |
| 打包部署 | WAR包部署到Tomcat | 独立JAR运行 |
| 监控 | 需自行集成 | Actuator内置监控 |
Q11:Spring Cloud和Spring Boot的关系?
A:Spring Cloud依赖Spring Boot构建微服务架构:
-
Spring Boot:快速构建单个微服务
-
Spring Cloud:解决微服务之间的通信、治理问题
-
包含组件:服务注册与发现(Eureka)、配置中心(Config)、网关(Gateway)、断路器(Hystrix)等
Q12:什么是响应式编程?Spring WebFlux是什么?
A:响应式编程是一种基于数据流和变化传播的异步编程范式:
-
特点:非阻塞、事件驱动、背压支持
-
WebFlux:Spring 5引入的响应式Web框架
-
核心类型:Mono(0-1个元素)、Flux(0-N个元素)
-
适用场景:高并发IO密集型应用(如网关、实时数据推送)
结语
Spring 5不仅仅是一个框架,更是一套优秀的设计思想的集合。通过本文的六个阶段,我们从最初的“手动new对象的痛苦”,逐步深入到IOC容器管理、AOP切面隔离、声明式事务管理,最终构建了一个完整的电商项目。
学习要点回顾
-
阶段一(问题锚定):理解了没有Spring时,代码耦合度高、测试困难、事务管理繁琐的痛点
-
阶段二(基础认知):掌握了IOC、AOP、事务的核心概念和基本原理
-
阶段三(用法拆解):学会了依赖注入的多种方式、AOP的切面编写、事务的传播行为和隔离级别
-
阶段四(场景融合):体验了电商下单流程中Spring各个特性的协同工作
-
阶段五(实战落地):完整实现了商品管理系统,包括缓存、AOP日志、事务管理等
-
阶段六(复盘升华):总结了最佳实践,分析了源码,梳理了面试重点
进阶学习建议
如果你已经掌握了本文的内容,可以继续深入学习:
-
Spring源码:阅读核心源码,理解设计模式的应用
-
Spring Boot:学习自动配置原理、起步依赖机制
-
Spring Cloud:掌握微服务架构的解决方案
-
Spring Security:学习认证授权框架
-
Spring Data:深入理解各种数据访问方式
-
Spring WebFlux:掌握响应式编程范式
最后的话
技术在不断演进,但设计思想是永恒的。无论框架如何变化,解耦、抽象、模块化这些核心思想始终不变。希望你在今后的开发中,不仅要会写@Autowired,更要理解这背后“解耦”与“抽象”的哲学。当你遇到问题时,不妨回到这六个阶段,锚定问题本质,再寻求解决方案。这才是Spring学习的终极奥义。
祝你在Java开发的道路上越走越远!
更多推荐




所有评论(0)