前言:为什么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的几个核心问题:

  1. 必须实现多个接口:一个简单的业务类需要实现EJBObject、EJBHome、SessionBean等多个接口

  2. 大量模板方法:即使不需要生命周期管理,也必须实现ejbCreate、ejbRemove等方法

  3. 强制处理RemoteException:即使本地调用也要处理远程异常

  4. 部署繁琐:需要编写复杂的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开发框架,其核心定位是降低企业级应用开发的复杂性。它通过以下方式解决上述问题:

  1. IOC容器:管理对象之间的依赖关系,实现解耦

  2. AOP支持:将日志、事务等横切关注点模块化

  3. 声明式事务:通过注解或配置管理事务,摆脱繁琐的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)。它的核心接口有两个:

  1. BeanFactory:最基本的容器,提供基础的DI支持

  2. 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);
}

解决流程

  1. Spring创建ServiceA,发现依赖ServiceB,将ServiceA的半成品放入三级缓存

  2. Spring创建ServiceB,发现依赖ServiceA,从三级缓存获取ServiceA的半成品注入

  3. ServiceB初始化完成,放入一级缓存

  4. 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 完整的电商下单流程

业务流程描述

  1. 用户在前端点击"下单"按钮

  2. 系统校验用户状态(是否登录、账户是否正常)

  3. 校验商品库存是否充足

  4. 扣减库存

  5. 创建订单(状态为待支付)

  6. 扣减用户余额(如果是余额支付)

  7. 发送消息到订单队列(异步处理后续流程)

  8. 记录操作日志

  9. 返回下单结果

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的生命周期可以分为以下几个阶段:

  1. 实例化:通过反射创建Bean实例

  2. 属性赋值:填充Bean的属性(依赖注入)

  3. Aware接口回调:调用BeanNameAware、BeanFactoryAware等

  4. BeanPostProcessor前置处理:调用postProcessBeforeInitialization

  5. 初始化:调用InitializingBean.afterPropertiesSet()和自定义init方法

  6. BeanPostProcessor后置处理:调用postProcessAfterInitialization

  7. 使用中:Bean准备就绪,等待使用

  8. 销毁:容器关闭时,调用DisposableBean.destroy()和自定义destroy方法

Q2:Spring如何解决循环依赖?

A:Spring通过三级缓存解决单例Bean的循环依赖问题:

  • 一级缓存:singletonObjects,存放完全初始化好的Bean

  • 二级缓存:earlySingletonObjects,存放早期暴露的Bean(半成品)

  • 三级缓存:singletonFactories,存放Bean工厂,用于生成代理对象

当A依赖B,B依赖A时:

  1. A创建过程中,提前暴露自己到三级缓存

  2. A依赖B,去创建B

  3. B创建过程中,发现依赖A,从三级缓存获取A的半成品

  4. B完成创建,放入一级缓存

  5. 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种失效场景:

  1. 非public方法

  2. 同一个类中的方法调用(自身调用)

  3. 异常被捕获未抛出

  4. 抛出检查异常(非RuntimeException)

  5. 多线程调用

  6. 数据库引擎不支持事务(如MyISAM)

  7. 传播行为设置错误(如NOT_SUPPORTED)

  8. 注解在接口上(不推荐)

  9. 方法被final修饰

  10. 事务管理器配置错误

  11. 数据库连接问题

  12. 类没有被Spring管理

6.4.3 事务相关面试题

Q7:Spring事务的传播行为有哪些?

A:7种传播行为:

  1. REQUIRED:支持当前事务,没有则新建(默认)

  2. SUPPORTS:支持当前事务,没有则以非事务方式执行

  3. MANDATORY:强制要求存在事务,否则抛异常

  4. REQUIRES_NEW:挂起当前事务,创建新事务

  5. NOT_SUPPORTED:以非事务方式执行,挂起当前事务

  6. NEVER:以非事务方式执行,存在事务则抛异常

  7. NESTED:嵌套事务(JDBC Savepoint)

Q8:Spring事务的隔离级别有哪些?

A:5种隔离级别:

级别 脏读 不可重复读 幻读 说明
DEFAULT - - - 数据库默认
READ_UNCOMMITTED 读未提交
READ_COMMITTED 读已提交
REPEATABLE_READ 可重复读
SERIALIZABLE 串行化

Q9:什么是分布式事务?Spring如何支持?

A:分布式事务涉及多个数据库或服务的事务一致性。Spring支持的方式:

  1. JTA(Java Transaction API):使用Atomikos等实现

  2. TCC(Try-Confirm-Cancel):手动实现补偿机制

  3. 可靠消息最终一致性:RabbitMQ + 本地消息表

  4. Seata:阿里开源的分布式事务框架

  5. 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切面隔离、声明式事务管理,最终构建了一个完整的电商项目。

学习要点回顾

  1. 阶段一(问题锚定):理解了没有Spring时,代码耦合度高、测试困难、事务管理繁琐的痛点

  2. 阶段二(基础认知):掌握了IOC、AOP、事务的核心概念和基本原理

  3. 阶段三(用法拆解):学会了依赖注入的多种方式、AOP的切面编写、事务的传播行为和隔离级别

  4. 阶段四(场景融合):体验了电商下单流程中Spring各个特性的协同工作

  5. 阶段五(实战落地):完整实现了商品管理系统,包括缓存、AOP日志、事务管理等

  6. 阶段六(复盘升华):总结了最佳实践,分析了源码,梳理了面试重点

进阶学习建议

如果你已经掌握了本文的内容,可以继续深入学习:

  1. Spring源码:阅读核心源码,理解设计模式的应用

  2. Spring Boot:学习自动配置原理、起步依赖机制

  3. Spring Cloud:掌握微服务架构的解决方案

  4. Spring Security:学习认证授权框架

  5. Spring Data:深入理解各种数据访问方式

  6. Spring WebFlux:掌握响应式编程范式

最后的话

技术在不断演进,但设计思想是永恒的。无论框架如何变化,解耦、抽象、模块化这些核心思想始终不变。希望你在今后的开发中,不仅要会写@Autowired,更要理解这背后“解耦”与“抽象”的哲学。当你遇到问题时,不妨回到这六个阶段,锚定问题本质,再寻求解决方案。这才是Spring学习的终极奥义。

祝你在Java开发的道路上越走越远!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐