Java 高级开发核心知识体系:Spring、MyBatis 与设计模式

📌 本文由 10 篇相关文章智能合并整理而成

Spring Boot 核心机制源码级分析:IoC 容器、AOP 代理与事务管理


title: “Spring Boot 核心机制源码级分析:IoC 容器、AOP 代理与事务管理的底层实现”

一、引言

Spring Boot 的”开箱即用”背后,是一套精巧的核心机制在支撑:IoC(控制反转)容器管理着所有 Bean 的生命周期,依赖注入框架织就了组件间的协作网络,AOP 代理在运行时透明地增强方法,而事务管理则借助 AOP 实现了声明式的事务边界控制。本文将逐一切入 Spring Boot 的这些核心机制,深入源码进行剖析。

二、IoC 容器与 Bean 生命周期

2.1 容器初始化流程

Spring 容器的初始化始于 ApplicationContext 的创建。以 AnnotationConfigApplicationContext 为例:

// 入口代码
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = 
            new AnnotationConfigApplicationContext(AppConfig.class);
        // 此时容器已初始化完毕
    }
}

内部经历了三大步骤:

1. 扫描Scan
   └─ 找到所有 @Component   BeanDefinition 注册到 BeanFactory

2. 推断构建方法
   └─ 确定每个 Bean 使用哪个构造器

3. 实例化 + 初始化生命周期回调
flowchart TD
    A[创建 ApplicationContext] --> B[注册配置类 BeanDefinition]
    B --> C[调用 refresh 方法]
    C --> D[invokeBeanFactoryPostProcessors]
    D --> E[扫描包路径, 注册所有 BeanDefinition]
    E --> F[registerBeanPostProcessors]
    F --> G[finishBeanFactoryInitialization]
    G --> H[实例化所有非懒加载单例 Bean]

    D --> D1[BeanFactoryPostProcessor 处理 @Configuration]
    E --> E1[{
'beanDefinitions': 所有类都被扫描为 BeanDefinition
}
] H --> H1[调用 getBean → doGetBean → createBean]

2.2 Bean 生命周期完整流程

flowchart LR
    A[BeanDefinition] --> B[实例化 Instantiation]
    B --> C[属性赋值 Populate]
    C --> D[Aware 接口回调]
    D --> E[BeanPostProcessor before]
    E --> F[初始化方法 Init]
    F --> G[BeanPostProcessor after]
    G --> H[就绪 Ready]
    H --> I[销毁 Destroy]

    D --> D1[BeanNameAware / BeanFactoryAware<br/>ApplicationContextAware]
    E --> E1[@PostConstruct / InitializingBean]
    F --> F1[@Bean(initMethod)]
    G --> G1[AOP 代理创建点]
    I --> I1[@PreDestroy / DisposableBean]

2.3 三级缓存与循环依赖的解决

Spring 通过三级缓存解决构造器注入之外的循环依赖:

// DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry {
    // 一级缓存:完整初始化的单例 Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    // 二级缓存:早期暴露的 Bean(还未完全初始化)
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

    // 三级缓存:ObjectFactory 延迟生成代理
    private final Map<String, ObjectFactory> singletonFactories = new HashMap<>(16);
}
级别 缓存名称 内容 用途
一级 singletonObjects 完全初始化好的 Bean 最终成品
二级 earlySingletonObjects 实例化但未初始化的 Bean 暴露半成品
三级 singletonFactories 生成代理的工厂 延迟创建 AOP 代理

循环依赖解决示例:

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}

解决过程:

1. 创建 A实例化 A  A 放入三级缓存  属性注入 B
2. 创建 B实例化 B  B 放入三级缓存  属性注入 A
3. B 注入 A从三级缓存取 A  执行 ObjectFactory  放入二级缓存
4. B 完成初始化从二级缓存/或者finalAOP代理put一级
5. A 完成初始化从一级取B注入A完成put一级

注意: 构造器注入无法解决循环依赖,因为构造器调用在实例化之前,此时三级缓存还未放入工厂。会导致 BeanCurrentlyInCreationException

三、依赖注入实现原理

3.1 @Autowired 注入处理

AutowiredAnnotationBeanPostProcessor 负责处理 @Autowired@Value 注解:

class AutowiredAnnotationBeanPostProcessor {
    @Override
    public PropertyValues postProcessProperties(
            PropertyValues pvs, Object bean, String beanName) {

        InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass());
        metadata.inject(bean, beanName, pvs);
        return pvs;
    }
}

注入方式对比:

注入方式 处理时机 实现类 优缺点
@Autowired 字段 BeanPostProcessor 阶段 AutowiredAnnotationBeanPostProcessor 简洁,不可测试
setter 注入 BeanPostProcessor 阶段 AutowiredAnnotationBeanPostProcessor 可选依赖
构造器注入 实例化阶段显式初始化参数 AnnotationConfigUtils 推荐,不可变

构造器注入的实现:

// Spring 遍历所有构造器,选最优的
@Component
public class UserService {
    private final UserRepository userRepository;

    // Spring 自动选这个构造器(唯一或最"肥"的)
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

内部通过 ConstructorResolver.autowireConstructor() 方法解析,按参数类型和 @Qualifier 查找匹配的 Bean。

四、AOP 代理机制

4.1 代理创建流程

Spring AOP 的入口是 AbstractAutoProxyCreator(一个 BeanPostProcessor),在每个 Bean 初始化之后判断是否生成代理:

public abstract class AbstractAutoProxyCreator {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof AopInfrastructureBean) {
            return bean;
        }

        // 检查是否有 Advisor 匹配该 Bean
        Advisor[] advisors = getAdvicesAndAdvisorsForBean(
                bean.getClass(), beanName, null);

        if (advisors != null) {
            // 创建代理对象
            return createProxy(bean.getClass(), beanName, 
                    specificInterceptors, new SingletonTargetSource(bean));
        }
        return bean;
    }
}

4.2 JDK 动态代理 vs CGLIB

特性 JDK 动态代理 CGLIB
实现方式 基于接口,运行时生成 Proxy 类 基于子类,继承增强
要求 目标类必须实现接口 无要求
代理方法调用效率 反射 + Method.invoke FastClass 机制(索引调用)
无法代理的场景 final 类、final 方法、static 方法
Spring Boot 默认 2.0 之前 2.0 之后(需设置 proxyTargetClass=true

Spring Boot 2.0+ 默认使用 CGLIB(即使有接口),因为:

  1. 现代框架普遍基于类(@Configuration 类等),而不是接口
  2. CGLIB 的 FastClass 机制比 JDK 反射更高效
  3. 统一代理方式可避免接口与实现类不一致导致的诡异 BUG

4.3 拦截器链执行

sequenceDiagram
    participant Client as 调用方
    participant Proxy as AOP 代理对象
    participant Chain as 拦截器链
    participant Target as 目标方法

    Client->>Proxy: userService.findUser()
    Proxy->>Chain: 调用拦截器链
    Note over Chain: 第一个拦截器: @Around
    Chain->>Chain: 第二个拦截器: @Before
    Chain->>Target: proceed() 调用目标
    Chain->>Chain: 第三个拦截器: @AfterReturning
    Chain-->>Proxy: 返回结果
    Proxy-->>Client: 结果

    Note over Chain: 责任链模式
    Note over Chain: 顺序: Around  Before  目标  After  AfterReturning

4.4 代理自调用失效问题

@Service
public class UserService {

    @Transactional
    public void methodA() {
        methodB(); // 自调用:不会触发事务
    }

    @Transactional
    public void methodB() {
        // 数据库操作
    }
}

原因: methodA() 调用 methodB() 时,是通过 this.methodB() 调用的,this 是目标对象而不是代理对象,因此不会触发事务增强。

解决方案:

// 1. 注入自身代理(Spring 5+ 可用 @Lazy 解决循环依赖)
@Autowired
@Lazy
private UserService self;

public void methodA() {
    self.methodB(); // 通过代理调用
}

// 2. 提取到另一个 Service(推荐)
public void methodA() {
    auditService.methodB(); // 独立 Bean
}

五、事务管理底层

5.1 事务代理的创建

@EnableTransactionManagement 会向容器中注册 BeanFactoryTransactionAttributeSourceAdvisor,加上 TransactionInterceptor(实现了 MethodInterceptor),共同组成一个事务 Advisor。

当某个 Bean 有 @Transactional 方法时,InfrastructureAdvisorAutoProxyCreator 创建代理,事务拦截器被织入:

public class TransactionInterceptor extends TransactionAspectSupport 
        implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 获取事务属性
        TransactionAttribute txAttr = getTransactionAttributeSource()
                .getTransactionAttribute(invocation.getMethod(), targetClass);

        // 获取 PlatformTransactionManager
        PlatformTransactionManager tm = getTransactionManager();

        // 执行带事务的调用
        return invokeWithinTransaction(invocation.getMethod(), targetClass, 
                invocation::proceed, txAttr, tm);
    }
}

5.2 事务传播行为实现

声明式事务的传播行为(Propagation)是 Spring 区别于原生 JDBC 事务的核心能力:

传播行为 含义 实现逻辑
REQUIRED(默认) 支持当前事务,没有则新建 doGetTransaction() → 有则加入
REQUIRES_NEW 必须新建事务,挂起当前事务 挂起当前事务,创建新 Connection
SUPPORTS 有就用,没有就不开 简单判断是否已有事务
MANDATORY 必须要有事务,否则抛异常 NoTransactionException
NOT_SUPPORTED 必须以非事务方式执行 挂起当前事务
NESTED 嵌套事务(JDBC Savepoint) 创建数据库级保存点

REQUIRES_NEW 的实现:

@Override
protected Object doBeginTransaction(
        Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = 
        (DataSourceTransactionObject) transaction;

    Connection con = txObject.getConnectionHolder().getConnection();

    // REQUIRES_NEW:挂起当前事务,获取新连接
    if (txObject.hasTransaction()) {
        suspend(txObject);  // 保存当前事务状态
    }

    // 设置自动提交关闭、隔离级别等
    con.setAutoCommit(false);
    // ...
}

5.3 事务隔离级别的底层实现

flowchart TD
    subgraph "隔离级别与问题"
        READ_UNCOMMITTED -->|脏读+不可重复读+幻读| LOW[并发高, 一致性低]
        READ_COMMITTED -->|不可重复读+幻读| MID[PG/默认]
        REPEATABLE_READ -->|幻读| HIGH
        SERIALIZABLE -->|无并发问题| HIGHEST[并发极低]
    end

实现差异:

数据库 如何实现 REPEATABLE_READ
MySQL InnoDB MVCC + Next-Key Lock(行锁 + Gap Lock)
PostgreSQL MVCC(不会幻读,但允许更新冲突)
Oracle 只支持 READ_COMMITTED 和 SERIALIZABLE

六、总结

机制 核心组件 关键源码入口 常见陷阱
IoC 容器 DefaultListableBeanFactory refresh()finishBeanFactoryInitialization() 构造器循环依赖
DI 注入 AutowiredAnnotationBeanPostProcessor postProcessProperties() 字段注入无法测试
AOP 代理 AbstractAutoProxyCreator postProcessAfterInitialization() 自调用失效
事务管理 TransactionInterceptor invokeWithinTransaction() 自调用 + REQUIRES_NEW 误解

掌握 Spring Boot 的底层机制不仅仅是”看源码”,而是建立对框架行为模式的预期。当你理解了 BeanPostProcessor 的链式调用、AbstractAutoProxyCreator 的触发时机,以及事务 TransactionInterceptor 在代理链中的位置之后,绝大多数 Spring Boot 的”奇怪问题”都会变得清晰可预测。


MyBatis 源码解析:从配置文件到 SQL 执行的完整链路

一、引言:从 JDBC 到 MyBatis

Java 开发者早期的数据库操作记忆可以用一个词概括:重复性劳动。每次数据库操作都需要:获取连接 → 创建 Statement → 设置参数 → 执行 SQL → 处理 ResultSet 映射为对象 → 关闭连接。这只是最基本的 CRUD 操作,就已经需要十几行样板代码。

// JDBC 的原始写法——每个方法都要重复的样板代码
public class UserJdbcDao {

    public User findById(Long id) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 1. 获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", 
                                                 "user", "password");

            // 2. 创建 PreparedStatement,设置参数
            ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?");
            ps.setLong(1, id);

            // 3. 执行 SQL
            rs = ps.executeQuery();

            // 4. 处理结果集,手动映射为对象
            User user = null;
            if (rs.next()) {
                user = new User();
                user.setId(rs.getLong("id"));
                user.setName(rs.getString("name"));
                user.setEmail(rs.getString("email"));
                // 如果表有 20 列,这里就要写 20 行
            }
            return user;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            // 5. 关闭资源(最容易忘记的一步)
            try { if (rs != null) rs.close(); } catch (Exception e) {}
            try { if (ps != null) ps.close(); } catch (Exception e) {}
            try { if (conn != null) conn.close(); } catch (Exception e) {}
        }
    }

    // 每个方法都要重复上述整个流程!
}

MyBatis 解决了这个问题,但不是简单地封装了 JDBC。它的核心价值在于将 SQL 的灵活性ORM 的便捷性 结合在了一起。

flowchart LR
    JDBC[ JDBC\n灵活但代码冗长] -->|MyBatis\n封装了这层| SEMI[SQL 写在 XML\n映射交给框架]
    Hibernate[全自动 ORM\nSQL 由框架生成] -->| SQL 调优困难| SEMI
    SEMI -->|MyBatis 的定位| M[半自动 ORM\nSQL 可控\n映射自动]

本文将从 MyBatis 的源码出发,深入拆解 MyBatis 是如何封装 JDBC 的——从配置加载、SQL 解析、参数绑定、结果集映射到核心执行器。

二、MyBatis 核心架构总览

2.1 整体分层

flowchart TD
    subgraph 接口层
        API[SqlSession\n核心 API]
        API2[SqlSessionFactory\n工厂]
    end

    subgraph 核心处理层
        C1[Configuration\n全局配置]
        MAP[MapperRegistry\n映射器注册]
        SQL[SqlSource\nSQL 语句封装]
        PA[ParameterHandler\n参数处理器]
        RS[ResultSetHandler\n结果集处理器]
        STMT[StatementHandler\nStatement 处理器]
        EXEC[Executor\n执行器]
    end

    subgraph 基础支持层
        D1[数据源模块]
        D2[事务管理]
        D3[缓存模块\n一级/二级]
        D4[TypeHandler\n类型处理器]
        D5[日志模块]
        D6[反射工具箱\nMetaObject]
    end

    subgraph JDBC
        JDBC_PKG[java.sql.*]
    end

    API --> EXEC
    EXEC --> STMT
    STMT --> PA
    STMT --> RS
    EXEC --> D3
    STMT --> JDBC_PKG

2.2 一次 SQL 查询的流转过程

// 一个典型的 MyBatis 查询
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findById(1L);

// 内部流转:
// 1. mapper.findById(1L) → MapperProxy.invoke()
// 2. → MapperMethod.execute()
// 3. → SqlSession.selectOne()
// 4. → Executor.query()
// 5. → StatementHandler.query()
// 6. → ParameterHandler.setParameters()
// 7. → PreparedStatement.executeQuery()
// 8. → ResultSetHandler.handleResultSets()
// 9. → 返回 User 对象

三、配置加载——SqlSessionFactoryBuilder

3.1 XMLConfigBuilder——配置文件的解析

MyBatis 启动时,SqlSessionFactoryBuilder 使用 XMLConfigBuilder 解析配置文件:

public class XMLConfigBuilder extends BaseBuilder {

    // 解析 MyBatis 全局配置文件的入口
    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;

        // 从 XML 根节点  开始,按顺序解析子元素:
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

    // 配置元素解析顺序(必须遵循这个顺序,否则报错)
    private void parseConfiguration(XNode root) {
        try {
            // 1. 解析 (外部属性文件)
            propertiesElement(root.evalNode("properties"));
            // 2. 解析 (全局配置参数)
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 3. 解析 
            loadCustomVfs(settings);
            loadCustomLogImpl(settings);
            // 4. 解析 (类型别名)
            typeAliasesElement(root.evalNode("typeAliases"));
            // 5. 解析 (拦截器插件)
            pluginElement(root.evalNode("plugins"));
            // 6. 解析 
            objectFactoryElement(root.evalNode("objectFactory"));
            // 7. 解析 
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 8. 解析 
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 9. 解析  应用到 Configuration
            settingsElement(settings);
            // 10. 解析 (数据源 + 事务管理器)
            environmentsElement(root.evalNode("environments"));
            // 11. 解析 
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 12. 解析 (自定义类型处理器)
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 13. 解析 (映射器文件)
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration.", e);
        }
    }
}

元素解析顺序的重要性: MyBatis 要求配置文件中各元素的书写顺序必须与 parseConfiguration 方法中的解析顺序一致。比如, 必须在 之前,因为 environments 可能引用 properties 中的变量。

四、Mapper 注册与代理

4.1 MapperRegistry——注册 Mapper 接口

public class MapperRegistry {

    private final Configuration config;
    // Mapper 接口 → MapperProxyFactory 的映射
    private final Map<Class, MapperProxyFactory> knownMappers = new HashMap<>();

    // 注册 Mapper 接口
    public <T> void addMapper(Class<T> type) {
        // 检查:必须是接口
        if (type.isInterface()) {
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // 创建 MapperProxyFactory
                knownMappers.put(type, new MapperProxyFactory<>(type));

                // 解析 Mapper 接口上的注解(@Options 等)
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
                    type.getResourceAsStream(type.getName().replace('.', '/') + ".xml"),
                    config, type.getName(), config.getSqlFragments());
                xmlMapperBuilder.parse();

                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
}

4.2 MapperProxy——JDK 动态代理的实现

// 为什么 Mapper 接口不需要实现类?
// 答案:通过 JDK 动态代理
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> methodCache;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果是 Object 类的方法,直接调用
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            // 处理默认方法(JDK 8+)
            if (method.isDefault()) {
                return invokeDefaultMethod(proxy, method, args);
            }

            // 核心:通过缓存获取 MapperMethodInvoker
            // 每个 MapperMethod 对应一个 SQL 操作
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
}

动态代理的组成:

flowchart LR
    subgraph 应用层
        M[UserMapper 接口] -->|getMapper 返回代理对象| MP[MapperProxy]
    end

    subgraph MyBatis 内部
        MP -->|invoke 调起| MM[MapperMethod]
        MM -->|execute| SS[SqlSession]
        SS -->|selectOne / selectList| EX[Executor]
    end

    subgraph JDBC 
        EX --> ST[StatementHandler]
        ST -->|PreparedStatement| DB[(Database)]
        DB --> RS[ResultSet]
        RS -->|handleResultSets| MM
        MM -->|返回结果| MP
        MP -->|返回| M
    end

五、SQL 解析——从 XML 到 SqlSource

5.1 XMLStatementBuilder——解析 Mapper XML

public class XMLStatementBuilder extends BaseBuilder {

    // 解析  节点
    public void parseStatementNode() {
        // 提取 SQL 节点的各个属性
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
        String parameterType = context.getStringAttribute("parameterType");
        String resultType = context.getStringAttribute("resultType");
        String resultMap = context.getStringAttribute("resultMap");
        String flushCache = context.getStringAttribute("flushCache");
        String useCache = context.getStringAttribute("useCache");

        // SQL 语句解析(核心)
        // 使用 XNodeParser 将包含 ${}、#{}、 的 SQL 文本
        // 解析为 SqlSource 对象
        SqlSource sqlSource = langDriver.createSqlSource(configuration, 
            context, parameterTypeClass);

        // 构建 MappedStatement(一个 MappedStatement 对应一个 Mapper 方法)
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(
            configuration, id, sqlSource, sqlCommandType);
        // ... 设置各种属性
    }
}

5.2 动态 SQL 的解析——OGNL 的魔法

MyBatis 的动态 SQL 是其最强功能之一。 等标签的解析通过 OGNL(Object-Graph Navigation Language) 来实现:

// 动态 SQL 的处理节点
public class IfSqlNode implements SqlNode {
    private final ExpressionEvaluator evaluator;
    private final String test;       // OGNL 表达式
    private final SqlNode contents;  // 子节点

    public IfSqlNode(SqlNode contents, String test) {
        this.test = test;
        this.contents = contents;
        this.evaluator = new ExpressionEvaluator();
    }

    @Override
    public boolean apply(DynamicContext context) {
        // 评估 OGNL 表达式
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            contents.apply(context);  // 条件满足 → 拼接 SQL
            return true;
        }
        return false;
    }
}

//  标签的实现
public class WhereSqlNode implements SqlNode {
    // 自动处理 WHERE 关键字和 AND/OR 前缀
    // 如果子节点以 AND 或 OR 开头,自动移除

    @Override
    public boolean apply(DynamicContext context) {
        // 1. 让子节点拼接 SQL
        contents.apply(context);
        // 2. 检查拼接结果:开头不能是 AND 或 OR
        // 3. 如果没有子节点有内容,就不加 WHERE
        // 4. 否则在前面加上 WHERE
    }
}

一个动态 SQL 的解析示例:

 id="findByCondition" resultType="User">
    SELECT * FROM user
    
         test="name != null">
            AND name = #{name}
        
         test="email != null">
            AND email = #{email}
        
    







六、参数处理——ParameterHandler

6.1 参数绑定的过程

// 参数绑定的核心——将用户传入的参数设置到 PreparedStatement
public class DefaultParameterHandler implements ParameterHandler {

    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;

    @Override
    public void setParameters(PreparedStatement ps) {
        // 遍历 SQL 中的参数占位符
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);

                // 获取参数值
                Object value;
                if (parameterMapping.getMode() == ParameterMode.OUT) {
                    // 存储过程输出参数
                    value = null;
                } else {
                    // 普通参数:通过 MetaObject 获取值
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(parameterMapping.getProperty());
                }

                // 获取类型处理器
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();

                // 设置参数到 PreparedStatement
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
            }
        }
    }
}

6.2 TypeHandler——类型转换的核心

// TypeHandler 接口——各种类型转换的抽象
public interface TypeHandler<T> {
    // Java → JDBC(写入数据库时)
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) 
        throws SQLException;

    // JDBC → Java(从数据库读取时,通过列名)
    T getResult(ResultSet rs, String columnName) throws SQLException;

    // JDBC → Java(通过列索引)
    T getResult(ResultSet rs, int columnIndex) throws SQLException;
}

// 例如:StringTypeHandler
public class StringTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, 
                                     JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter);
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getString(columnName);
    }
}

// 自定义枚举 TypeHandler
public class GenderTypeHandler extends BaseTypeHandler<Gender> {
    // 将 Gender 枚举转为数据库的 VARCHAR
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Gender parameter, 
                                     JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.getCode());
    }

    @Override
    public Gender getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return Gender.fromCode(rs.getString(columnName));
    }
}

七、执行器(Executor)与缓存

7.1 执行器层级

flowchart TD
    subgraph Executor 层级
        BASE[BaseExecutor\n抽象基类]
        SIMPLE[SimpleExecutor\n每次执行创建 Statement]
        REUSE[ReuseExecutor\n缓存 Statement 复用]
        BATCH[BatchExecutor\n批量执行]
        CACHE[CachingExecutor\n二级缓存装饰器]
    end

    BASE --> SIMPLE
    BASE --> REUSE
    BASE --> BATCH
    CACHE -->|装饰| BASE

    style CACHE fill:#27ae60,color:#fff

7.2 一级缓存

public abstract class BaseExecutor implements Executor {

    // 一级缓存:PerpetualCache(HashMap)
    protected PerpetualCache localCache;

    // 查询逻辑
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, 
                              RowBounds rowBounds, ResultHandler resultHandler,
                              CacheKey key, BoundSql boundSql) throws SQLException {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }

        // 清空一级缓存的时机:flushCache=true 或执行了 update 操作
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }

        List<E> list;
        try {
            queryStack++;

            // 从一级缓存中查找
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;

            if (list != null) {
                // 缓存命中
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 缓存未命中 → 从数据库查询
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            queryStack--;
        }

        if (queryStack == 0) {
            // 延迟加载的处理
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            deferredLoads.clear();

            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // STATEMENT 级别:查询完成后立即清除缓存
                clearLocalCache();
            }
        }

        return list;
    }

    // 一级缓存的生命周期:
    // 1. 同一个 SqlSession 中,同一个 SQL + 参数,第二次查询直接返回缓存
    // 2. 执行 UPDATE/INSERT/DELETE 后,清除缓存
    // 3. SqlSession close 或 commit 后,清除缓存
    // 4. 跨 SqlSession 不共享(一级缓存是 SqlSession 级别的)
}

7.3 二级缓存

// CachingExecutor 通过装饰器模式为 Executor 添加二级缓存
public class CachingExecutor implements Executor {

    private final Executor delegate;  // 被装饰的 Executor
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, 
                              RowBounds rowBounds, ResultHandler resultHandler,
                              CacheKey key, BoundSql boundSql) throws SQLException {

        // 获取 MappedStatement 关联的二级缓存
        Cache cache = ms.getCache();

        if (cache != null) {
            // 先尝试从二级缓存获取
            List<E> list = tcm.getObject(cache, key);

            if (list == null) {
                // 二级缓存未命中 → 委派给底层 Executor 查询
                list = delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);

                // 查询结果放入二级缓存
                tcm.putObject(cache, key, list);
            }

            return list;
        }

        // 没有二级缓存 → 直接查询
        return delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
}
缓存 级别 生命周期 默认是否开启 适用范围
一级缓存 SqlSession 级别 SqlSession 生命周期内 开启 同一个 SqlSession 内的重复查询
二级缓存 Mapper 级别(跨 SqlSession) 整个应用运行期间 关闭 跨 SqlSession 的重复查询

八、结果集映射——ResultSetHandler

8.1 从 ResultSet 到 Java 对象

public class DefaultResultSetHandler implements ResultSetHandler {

    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        List<Object> multipleResults = new ArrayList<>();

        int resultSetCount = 0;
        ResultSetWrapper rsw = new ResultSetWrapper(stmt.getResultSet(), configuration);

        // 处理多结果集(存储过程等场景)
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        while (rsw != null && resultMapCount > resultSetCount) {
            ResultMap resultMap = resultMaps.get(resultSetCount);

            // 核心:将 ResultSet 当前行映射为 Java 对象
            handleResultSet(rsw, resultMap, multipleResults, null);

            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }

        // 处理 ResultSet 结果
        return collapseSingleResultList(multipleResults);
    }

    // 单行映射
    private Object handleRowValueForResultMap(ResultSetWrapper rsw, 
                                               ResultMap resultMap,
                                               CacheKey combinedKey,
                                               String columnPrefix,
                                               Object partialObject) throws SQLException {

        // 创建默认映射对象(通过无参构造器)
        Object resultObject = createResultObject(rsw, resultMap, constructor, columnPrefix);

        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            // 创建 MetaObject 用于设置属性
            MetaObject metaObject = configuration.newMetaObject(resultObject);

            // 遍历结果映射
            List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
            for (ResultMapping propertyMapping : propertyMappings) {
                // 处理嵌套映射(association、collection)
                if (propertyMapping.getNestedResultMapId() != null) {
                    handleRowValuesForNestedResultMap(rsw, resultMap, ...);
                } else {
                    // 普通列 → 属性映射
                    String columnName = propertyMapping.getColumn();
                    TypeHandler typeHandler = propertyMapping.getTypeHandler();
                    Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
                    metaObject.setValue(propertyMapping.getProperty(), value);
                }
            }
        }

        return resultObject;
    }
}

九、插件体系——拦截器的实现

MyBatis 的插件机制允许你在 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四个核心对象的方法调用过程中插入自定义逻辑。

// 插件本质上是通过 JDK 动态代理实现的拦截器

@Intercepts({
    @Signature(type = Executor.class, 
               method = "update",
               args = {MappedStatement.class, Object.class})
})
public class SqlMonitorPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();

        try {
            // 执行原方法
            return invocation.proceed();
        } finally {
            long duration = System.currentTimeMillis() - start;

            MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
            String sqlId = ms.getId();

            if (duration > 1000) {
                log.warn("Slow SQL [{}] took {}ms", sqlId, duration);
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        // 使用 Plugin.wrap 创建代理
        return Plugin.wrap(target, this);
    }
}

十、总结

  1. MyBatis 的核心是 JDBC 的优雅封装。 它将连接管理、参数设置、结果映射这些重复劳动抽象化,通过配置和注解实现了 SQL 与 Java 代码的分离。

  2. 动态代理是 Mapper 接口无需实现类的秘密。 MapperProxy 通过 JDK 动态代理将接口方法调用转换为 SqlSession 操作,这是整个 MyBatis-Spring 集成的基础。

  3. XML 解析是 MyBatis 的心脏。 从全局配置到 SQL 映射,XMLStatementBuilder 和 SqlSource 将动态 SQL 解析为可执行的 BoundSql,OGNL 表达式为动态 SQL 注入了灵活性。

  4. 缓存架构体现了”先读缓存,后读数据库”的思想。 一级缓存自动生效减少重复查询,二级缓存通过装饰器模式提供跨 SqlSession 的缓存共享,但需要谨慎使用(脏数据风险)。

  5. TypeHandler 是类型转换的桥梁。 MyBatis 内置了丰富的类型处理器,同时支持自定义 TypeHandler 以应对特殊枚举或复杂类型的转换需求。

  6. 插件体系是 MyBatis 扩展性的体现。 拦截四个核心对象的方法调用,可以无侵入地实现 SQL 监控、分页、性能统计等功能。


如何设置 MySQL 事务隔离级别

如何设置 MySQL 事务隔离级别

概述

MySQL 提供了灵活的方式来设置事务隔离级别,支持全局级别会话级别下一个事务级别三种作用域。掌握这些设置方法,是日常开发和管理 MySQL 的基础技能。

隔离级别种类

MySQL 支持的四种隔离级别:

级别名称 数值代号 描述
READ UNCOMMITTED 0 读未提交
READ COMMITTED 1 读已提交
REPEATABLE READ 2 可重复读(默认)
SERIALIZABLE 3 可串行化

设置语法

1. 全局级别(影响所有新连接)

-- 语法
SET GLOBAL TRANSACTION ISOLATION LEVEL 级别名称;
-- 或
SET @@global.tx_isolation = '级别名称';
-- MySQL 8.0+ 新系统变量
SET @@global.transaction_isolation = '级别名称';

-- 示例
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET @@global.tx_isolation = 'READ-COMMITTED';

作用范围:设置后,新建立的连接会使用该级别。已存在的连接不受影响。

2. 会话级别(影响当前连接)

-- 语法
SET SESSION TRANSACTION ISOLATION LEVEL 级别名称;
-- 或
SET @@session.tx_isolation = '级别名称';

-- 示例
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET @@session.tx_isolation = 'REPEATABLE-READ';

作用范围:仅当前会话有效,断开连接后失效。

3. 下一个事务级别(仅影响下一个事务)

-- 语法
SET TRANSACTION ISOLATION LEVEL 级别名称;

-- 示例
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

作用范围:仅影响当前会话中的下一个事务。执行完该事务后,恢复为会话级别的隔离级别。

查看当前隔离级别

查看会话级别

SELECT @@session.tx_isolation;
-- 或(MySQL 8.0 推荐)
SELECT @@session.transaction_isolation;

-- 结果示例
-- +-------------------------------+
-- | @@session.tx_isolation        |
-- +-------------------------------+
-- | REPEATABLE-READ              |
-- +-------------------------------+

查看全局级别

SELECT @@global.tx_isolation;
-- 或
SELECT @@global.transaction_isolation;

通过配置文件持久化设置

my.cnf / my.ini

[mysqld]
# 设置默认隔离级别
transaction-isolation = READ-COMMITTED

# 可选值:
# READ-UNCOMMITTED
# READ-COMMITTED
# REPEATABLE-READ(默认)
# SERIALIZABLE

设置后需要重启 MySQL 服务才能生效:

# 重启 MySQL
systemctl restart mysqld

MySQL 8.0 的变化

系统变量更名

MySQL 8.0 移除了旧的 tx_isolation 变量,改用新名:

-- MySQL 5.x 兼容写法
SET @@session.tx_isolation = 'READ-COMMITTED';
SELECT @@session.tx_isolation;

-- MySQL 8.0 推荐写法
SET @@session.transaction_isolation = 'READ-COMMITTED';
SELECT @@session.transaction_isolation;

新增特性

MySQL 8.0 支持在 SET TRANSACTION 时指定作用域更加明确:

-- 这些在 8.0 中仍有效
SET PERSIST transaction_isolation = 'READ-COMMITTED';  -- 持久化到数据目录 + 运行时
SET PERSIST_ONLY transaction_isolation = 'READ-COMMITTED';  -- 仅持久化,下次重启生效

实践建议

1. 开发环境

-- 在应用连接池初始化时设置
-- Java (JDBC):
-- jdbc:mysql://localhost:3306/db?transactionIsolation=READ_COMMITTED

-- Python (SQLAlchemy):
-- engine = create_engine('mysql://...', isolation_level="READ_COMMITTED")

2. 性能调优

-- 如果业务可以接受,推荐降级到 READ COMMITTED
-- 可以减少间隙锁带来的锁冲突
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

3. 临时调试

-- 排查问题时临时提升隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 执行怀疑有并发问题的查询
-- ... 执行完切回正常级别

面试要点

  1. 设置方式:全局(GLOBAL)、会话(SESSION)、下一个事务(无前缀)
  2. 生效范围:全局不影响已有连接;会话不影响当前正在执行的事务
  3. MySQL 8.0 变化tx_isolationtransaction_isolation
  4. 配置持久化:my.cnf 或 SET PERSIST
  5. 应用端配置:连接池或 JDBC URL 中配置

四种事务隔离级别

四种事务隔离级别

什么是隔离级别

隔离级别定义了一个事务对另一个事务的可见程度。MySQL InnoDB 支持四种隔离级别,从低到高:

级别 名称 脏读 不可重复读 幻读
0 READ UNCOMMITTED ✅ 可能 ✅ 可能 ✅ 可能
1 READ COMMITTED ❌ 不会 ✅ 可能 ✅ 可能
2 REPEATABLE READ ❌ 不会 ❌ 不会 ✅ 可能(InnoDB 解决)
3 SERIALIZABLE ❌ 不会 ❌ 不会 ❌ 不会

✅ = 可能发生 ❌ = 不会发生

READ UNCOMMITTED(读未提交)

行为

事务可以读到其他事务未提交的数据。

-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 事务 A
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 未提交

-- 事务 B
-- 可以读取 A 未提交的数据!
SELECT balance FROM accounts WHERE id = 1;
-- 返回:修改后的值(比如 400)

脏读

事务 B 读到了事务 A 尚未提交的修改。如果事务 A 随后 ROLLBACK,事务 B 读到的就是”脏数据”。

脏读的后果:
事务 A 扣了 100 元(未提交)
事务 B 读到余额 400 元
事务 A 回滚 → 余额回到 500 元
事务 B 基于 400 元做决策 → 业务不一致!

使用场景

几乎不用。唯一场景可能是"我只需要一个大概值"的监控查询。
但即便如此,也不推荐。

READ COMMITTED(读已提交)

行为

只能读到已提交的数据,解决了脏读问题。

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 事务 A
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 未提交

-- 事务 B
SELECT balance FROM accounts WHERE id = 1;
-- 返回:旧值 500(读不到未提交的修改)

-- 事务 A 提交
COMMIT;

-- 事务 B 再次查询
SELECT balance FROM accounts WHERE id = 1;
-- 返回:新值 400(可以读到已提交的修改)

不可重复读问题

同一个事务中,两次相同查询可能返回不同结果

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

START TRANSACTION;

SELECT balance FROM accounts WHERE id = 1;
-- 第一次:500

-- 其他事务此时提交了修改
-- UPDATE accounts SET balance = 400 WHERE id = 1; COMMIT;

SELECT balance FROM accounts WHERE id = 1;
-- 第二次:400(同个事务中值变了!)

COMMIT;

使用场景

- 很多数据库的默认级别(如 Oracle、PostgreSQL)
- 适合:对一致性要求不是极端严格,但需要避免脏读
- 不适合:一个事务需要多次读取同一数据并做业务判断

REPEATABLE READ(可重复读)

行为

MySQL 的默认隔离级别。同一个事务中,第一次读取后,后续相同读取始终看到同样的结果

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

START TRANSACTION;

SELECT balance FROM accounts WHERE id = 1;
-- 第一次:500

-- 即使其他事务修改并提交
-- UPDATE accounts SET balance = 400 WHERE id = 1; COMMIT;

SELECT balance FROM accounts WHERE id = 1;
-- 第二次:500(不变!)

SELECT balance FROM accounts WHERE id = 1;
-- 第三次:500(始终不变)

COMMIT;

幻读问题

理论上 REPEATABLE READ 存在幻读问题:同一个事务中,两次相同条件的范围查询返回不同行数。

-- 理论上的幻读
-- 事务 A
SELECT * FROM orders WHERE amount > 100;
-- 返回:10 行

-- 其他事务插入了一行 amount=200 并提交

SELECT * FROM orders WHERE amount > 100;
-- 理论上应该返回 11 行(多了 1 行 "幻影")

-- 但 InnoDB 的 REPEATABLE READ 不会!
-- 因为 InnoDB 用间隙锁 + MVCC 解决了幻读

InnoDB 用 Next-Key Lock(临键锁) 在 REPEATABLE READ 级别下实际上解决了幻读问题。所以 MySQL 的 RR 级别比标准 SQL 的 RR 更强。

使用场景

-- 适合场景:需要事务内多次读取数据一致
-- 比如:报表生成、数据导出无需每次都重新查

-- MySQL 的默认级别
-- 大多数应用可以放心使用

SERIALIZABLE(可串行化)

行为

最强的隔离级别,所有事务串行执行,完全杜绝并发问题。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

START TRANSACTION;

-- 普通的 SELECT 也会加锁
SELECT * FROM accounts WHERE id = 1;
-- 自动转为 SELECT ... LOCK IN SHARE MODE
-- 其他事务无法修改 id=1 的数据

-- 直到事务提交,锁才释放
COMMIT;

性能代价

SERIALIZABLE 对所有读操作自动加 S 锁:
- 读读:不冲突
- 读写:互斥!
- 性能下降明显(并发度极低)

几乎所有场景都不推荐,除非:
- 数据一致性高于一切
- 并发极低或没有
- 需要调试/测试时

隔离级别的设置

-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- MySQL 8.0:REPEATABLE READ

-- 设置全局
SET GLOBAL transaction_isolation = 'READ-COMMITTED';

-- 设置当前会话
SET SESSION transaction_isolation = 'READ-COMMITTED';

-- 设置下一个事务
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 配置文件(永久)
-- [mysqld]
-- transaction-isolation = READ-COMMITTED

隔离级别的实践建议

业务场景                   推荐隔离级别
──────────────────────────────────────
大多数 Web 应用            REPEATABLE READ(MySQL 默认)
高并发、数据一致性要求一般  READ COMMITTED
数据导出、长事务报表查询    REPEATABLE READ
银行/金融核心系统          SERIALIZABLE(或 RR+显式锁)
几乎不会用                 READ UNCOMMITTED

面试要点

  • 四种隔离级别(从低到高):RU → RC → RR → Serializable
  • 三个并发问题:脏读(RU)、不可重复读(RC)、幻读(RR 基本解决)
  • MySQL 默认:REPEATABLE READ(与 Oracle/PostgreSQL 不同,后两者默认 RC)
  • MySQL 的 RR 更强:通过间隙锁解决了幻读,实际上接近 Serializable
  • 「读已提交 vs 可重复读」的核心区别:Read View 的创建时机不同
  • RU 和 Serializable 很少用:一个极端不安全,一个极端慢

一句话总结:隔离级别是并发与一致的交易——RU 放弃了隔离换取性能,SERIALIZABLE 放弃性能换取绝对隔离;MySQL 的默认 RR 用 MVCC 和间隙锁达到了”读不阻塞写、写不阻塞读、还能防幻读”的较好平衡。


Spring核心特性详解:IoC控制反转与AOP面向切面编程

Spring核心特性详解:IoC控制反转与AOP面向切面编程

一、定义

Spring 框架的两大核心特性:

  • IoC(Inversion of Control,控制反转):将对象的创建和依赖关系的管理交给 Spring 容器,由”程序员主动创建”变为”容器自动注入”
  • AOP(Aspect Oriented Programming,面向切面编程):通过预编译和运行期动态代理实现程序功能的统一维护,将横切关注点与业务逻辑分离

二、IoC(控制反转)原理

传统方式 vs IoC 方式

flowchart TD
    subgraph 传统方式
        T1["UserService"] --> T2["new UserDao()"]
        T2 --> T3["new JdbcTemplate()"]
        T3 --> T4["new DataSource()"]
        T1 -.->|"反向依赖
主动创建所有依赖"
| T4 end subgraph IoC方式 I1["UserService
@Autowired"
] -- "请求" --> I2["IoC容器"] I2 --> I3["UserDao 实例"] I3 --> I4["JdbcTemplate 实例"] I4 --> I5["DataSource 实例"] I2 -.->|"正转向
容器自动注入"
| I1 end
// ❌ 传统方式:对象自己创建依赖(紧耦合)
public class UserService {
    private UserDao userDao = new UserDao(); // 硬编码创建

    public User findUser(Long id) {
        return userDao.findById(id);
    }
}

// ✅ IoC 方式:容器注入依赖(松耦合)
public class UserService {
    private final UserDao userDao; // 依赖由外部注入

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public User findUser(Long id) {
        return userDao.findById(id);
    }
}

IoC 的核心优势

优势 说明
解耦 调用者不再关心被调用者的创建细节
可测试性 方便 Mock 替换,单元测试更简单
灵活性 可以在不修改代码的情况下更换实现
生命周期管理 容器自动管理 Bean 的创建、初始化、销毁

三、DI(依赖注入)三种方式

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    // 1️⃣ 字段注入(Field Injection)— ❌ 不推荐
    @Autowired
    private OrderDao orderDao;

    // 2️⃣ 构造器注入(Constructor Injection)— ✅ 推荐
    private final PaymentService paymentService;
    private final InventoryService inventoryService;

    @Autowired
    public OrderService(PaymentService paymentService,
                        InventoryService inventoryService) {
        this.paymentService = paymentService;
        this.inventoryService = inventoryService;
    }

    // 3️⃣ Setter 注入(Setter Injection)— ⚠️ 可选注入时使用
    private DiscountService discountService;

    @Autowired
    public void setDiscountService(DiscountService discountService) {
        this.discountService = discountService;
    }
}

三种注入方式对比

flowchart TD
    SUBJ["选择注入方式"] --> Q1{"字段是否需要可变?"}
    Q1 -->|"否(推荐)"| CON["构造器注入
✅ 推荐
final 不可变
保证不为null
方便测试"
] Q1 -->|"是"| Q2{"是否存在循环依赖?"} Q2 -->|"是"| SET["Setter注入
可解决循环依赖
(但应重构避免)"
] Q2 -->|"否"| CON SET -.->|"不推荐"| FIELD["字段注入
@Autowired on field
代码最简洁
但无法测试"
]

四、AOP(面向切面编程)原理

AOP 核心概念

flowchart TD
    subgraph 业务逻辑
        A["方法A"] -->|"执行"| B["方法B"]
        B --> C["方法C"]
    end

    subgraph 横切关注点-AOP
        LOG["切面: 日志记录"]
        TX["切面: 事务管理"]
        SEC["切面: 权限校验"]
    end

    LOG -.->|"@Before"| A
    TX -.->|"@Around"| B
    SEC -.->|"@Before"| B
    LOG -.->|"@AfterReturning"| C
概念 含义 类比
Aspect(切面) 横切关注点的模块化 日志模块
Join Point(连接点) 程序执行中的某个点 每个方法调用、异常等
Pointcut(切入点) 匹配连接点的表达式 “哪些方法需要日志”
Advice(通知) 在切入点执行的动作 “打印日志这个动作”
Target(目标对象) 被代理的对象 UserService 实例
Proxy(代理) 生成的代理对象 JDK 动态代理 / CGLIB 代理
Weaving(织入) 将切面应用到目标对象的过程 编译期/类加载期/运行期

五种通知类型

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    // 定义切入点(可复用)
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    // 1. @Before:方法执行前
    @Before("serviceMethods()")
    public void beforeAdvice() {
        System.out.println("【前置通知】方法开始执行");
    }

    // 2. @AfterReturning:方法正常返回后(可获取返回值)
    @AfterReturning(value = "serviceMethods()", returning = "result")
    public void afterReturningAdvice(Object result) {
        System.out.println("【返回通知】返回结果: " + result);
    }

    // 3. @AfterThrowing:方法抛出异常后
    @AfterThrowing(value = "serviceMethods()", throwing = "ex")
    public void afterThrowingAdvice(Throwable ex) {
        System.out.println("【异常通知】异常: " + ex.getMessage());
    }

    // 4. @After:方法结束后(finally 语义,不管是否异常)
    @After("serviceMethods()")
    public void afterAdvice() {
        System.out.println("【后置通知】方法执行结束(finally)");
    }

    // 5. @Around:环绕通知(最强大,可控制方法执行)
    @Around("serviceMethods()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("【环绕前置】方法: " + pjp.getSignature().getName());

        Object result = pjp.proceed(); // 执行目标方法

        long elapsed = System.currentTimeMillis() - start;
        System.out.println("【环绕后置】耗时: " + elapsed + "ms");
        return result;
    }
}

五、AOP 的典型应用场景

场景 实现方式 常见注解
事务管理 @Transactional 底层是 AOP @Transactional
日志记录 @Before / @Around 记录方法调用 @Before("execution(*)")
权限校验 @Before 校验用户权限 @PreAuthorize, @Secured
性能监控 @Around 统计执行时间 自定义注解
缓存处理 @Cacheable 等实现方法返回值缓存 @Cacheable, @CacheEvict
异常处理 @AfterThrowing 统一异常处理 自定义注解

六、Spring 整体架构

flowchart TD
    subgraph Spring Framework
        CORE["Core Container
核心容器"
] AOP["AOP + Aspects
切面编程"
] DATA["Data Access
数据访问"
] WEB["Web
Web 层"
] TEST["Test
测试"
] end subgraph Core Container BEANS["Beans
BeanFactory"
] CORE_CORE["Core
IoC容器"
] CONTEXT["Context
ApplicationContext"
] SPEL["SpEL
表达式语言"
] end subgraph Data Access JDBC["JDBC
JdbcTemplate"
] ORM["ORM
JPA/MyBatis"
] TX["Transactions
事务管理"
] end subgraph Web WS["Web MVC
Spring MVC"
] WSOCKET["WebSocket"] end CORE --> AOP CORE --> DATA CORE --> WEB CORE --> TEST

七、面试常见问题

Q1:IoC 和 DI 的关系是什么?
IoC 是一种设计思想——控制反转,即”别找我,我来找你”。DI(依赖注入)是 IoC 的具体实现方式。Spring 通过 DI(构造器注入、Setter 注入、字段注入)来实现 IoC 的设计思想。

Q2:AOP 有哪些实现方式?
编译期:AspectJ 修改字节码(编译时织入);② 类加载期:Load-time Weaving(加载时织入);③ 运行期:Spring AOP(JDK 动态代理 / CGLIB)。Spring AOP 默认使用运行期动态代理。

Q3:Spring AOP 和 AspectJ 的区别?
Spring AOP 基于动态代理(运行期),只支持方法级别的连接点;AspectJ 可以织入构造器、字段访问、静态初始化等,功能更强大但配置更复杂。Spring AOP 通常与 AspectJ 的注解配合使用。

Q4:为什么 Spring 推荐构造器注入?
① 保证依赖不可变(final 关键字);② 保证依赖不为空(NullPointerException 风险低);③ 方便单元测试(不需要反射或 Spring 容器);④ 可以避免部分循环依赖问题。

Q5:@Autowired 和 @Resource 的区别?
@Autowired:Spring 注解,默认按类型注入
@Resource:JSR-250 注解,默认按名称注入(name 属性指定)

>

IoC原理详解:控制反转与依赖注入的深入分析

IoC原理详解:控制反转与依赖注入的深入分析

一、定义

IoC(Inversion of Control,控制反转) 是一种设计思想,将对象的创建权依赖关系的管理权从程序代码中转移到容器中。DI(Dependency Injection,依赖注入) 是 IoC 的具体实现方式。

好莱坞原则(Hollywood Principle):“别打电话给我,我会打给你。” ——对象不需要主动查找或创建依赖,容器会将依赖注入进来。

二、传统方式 vs IoC 方式

flowchart LR
    subgraph 传统方式:正向控制
        A["UserService"] -->|"主动创建"| B["UserDao"]
        B -->|"主动创建"| C["DataSource"]
        A -.->|"高耦合
修改需改代码"
| C end subgraph IoC方式:控制反转 D["UserService"] -->|"申请依赖"| E["IoC容器"] E -->|"管理并注入"| F["UserDao
(由容器创建)"
] E -->|"管理并注入"| G["DataSource
(由容器创建)"
] E -.->|"松耦合
配置化"
| D end

代码对比

// ❌ 传统方式 — 主动创建依赖
public class OrderService {
    private OrderDao orderDao = new OrderDao();      // 硬编码
    private PaymentService paymentService = new PaymentService(); // 硬编码

    public void createOrder(Order order) {
        orderDao.save(order);
        paymentService.pay(order.getAmount());
    }
}

// ✅ IoC 方式 — 由容器注入
@Service
public class OrderService {
    private final OrderDao orderDao;
    private final PaymentService paymentService;

    // 构造器注入:依赖由 IoC 容器自动提供
    public OrderService(OrderDao orderDao, PaymentService paymentService) {
        this.orderDao = orderDao;
        this.paymentService = paymentService;
    }

    public void createOrder(Order order) {
        orderDao.save(order);
        paymentService.pay(order.getAmount());
    }
}

三、IoC 容器的核心接口

BeanFactory vs ApplicationContext

flowchart TD
    subgraph IoC容器体系
        BF["BeanFactory
顶级接口
延迟加载"
] AC["ApplicationContext
BeanFactory的子接口
预加载"
] end BF -->|"功能:
getBean()
isSingleton()"
| CORE["核心IoC功能"] AC -->|"额外功能:
国际化(MessageSource)
事件发布
AOP集成
资源访问"
| EXT["高级容器功能"] subgraph 常用实现 XML["ClassPathXmlApplicationContext
XML配置"
] ANNOT["AnnotationConfigApplicationContext
注解配置"
] WEB["WebApplicationContext
Web应用"
] end XML --> AC ANNOT --> AC WEB --> AC
对比维度 BeanFactory ApplicationContext
Bean 加载 懒加载(使用时才创建) 预加载(启动时创建所有单例 Bean)
国际化 ❌ 不支持 ✅ 支持
事件发布 ❌ 不支持 ✅ 支持
AOP 集成 手动集成 自动集成
应用场景 资源受限环境(移动/嵌入式) 企业应用(绝大多数场景)

四、依赖注入的 3 种方式

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class PaymentService {

    // 1️⃣ 字段注入(Field Injection)— 不推荐
    @Autowired
    private PaymentGateway paymentGateway;

    // 2️⃣ 构造器注入(Constructor Injection)— ✅ 推荐
    private final TransactionRepository transactionRepository;
    private final PolicyService policyService;

    public PaymentService(TransactionRepository transactionRepository,
                          PolicyService policyService) {
        this.transactionRepository = transactionRepository;
        this.policyService = policyService;
    }

    // 3️⃣ Setter 注入(Setter Injection)— 可选注入
    private NotificationService notificationService;

    @Autowired
    public void setNotificationService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }
}
注入方式 可靠性 可测试性 不可变性 循环依赖处理
构造器注入 ✅ 对象创建后依赖可靠 ✅ 无需 Spring ✅ final 支持 ❌ 无法解决
Setter 注入 ⚠️ 依赖可选 ✅ 易替换 ❌ 可修改 ✅ 可解决
字段注入 ❌ 依赖不明确 ❌ 需反射/容器 ❌ 可修改 ❌ 不推荐

五、IoC 容器的工作流程

sequenceDiagram
    participant APP as 应用程序
    participant CTX as IoC容器<br>ApplicationContext
    participant REG as BeanDefinitionRegistry
    participant FACT as BeanFactory
    participant BEAN as Bean实例

    APP->>CTX: 初始化容器<br>new AnnotationConfigApplicationContext(...)
    CTX->>CTX: 扫描包/解析配置类/XML
    CTX->>REG: 注册 BeanDefinition
    REG->>FACT: 开始创建 Bean

    Note over FACT: 单例 Bean 预实例化

    FACT->>BEAN: 实例化(反射创建对象)
    BEAN->>BEAN: 依赖注入(populateBean
    BEAN->>BEAN: 初始化(InitializingBean/init-method
    BEAN-->>CTX: Bean 就绪

    CTX-->>APP: 容器初始化完成
    APP->>CTX: getBean("userService")
    CTX-->>APP: 返回 UserService Bean

六、@Component 和 @Bean 的区别

// 方式一:@Component + 类扫描
@Component
public class MyService {
    public void doSomething() { }
}

// 方式二:@Bean + 配置类
@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyService(); // 方法体可包含复杂逻辑
    }
}
对比维度 @Component @Bean
使用位置 类上 方法上
控制力 较低(Spring 控制创建) 较高(方法体控制创建)
适用场景 自己编写的类 第三方库、复杂初始化
数量 类上有多个注解 一个方法创建一个 Bean
扫描方式 组件扫描 配置类加载

七、常用注解

注解 用途 等效于
@Component 通用组件
@Service 业务层 @Component 语义化
@Repository 数据访问层 DAO 异常转换
@Controller Web 层 Spring MVC 控制器
@Autowired 依赖注入 byType 注入
@Qualifier 指定 Bean 名称 byName 注入
@Value 注入配置值 ${property}
@Scope 指定作用域 singleton/prototype

八、面试常见问题

Q1:IoC 和 DI 是一回事吗?
不是。IoC 是设计思想(控制反转:谁控制谁的问题),DI 是实现手段(谁注入给谁的问题)。IoC 可以有多种实现方式:DI、DL(Dependency Lookup)、Service Locator 等。Spring 通过 DI 实现了 IoC 思想。

Q2:Spring 如何解决循环依赖?
Spring 使用三级缓存(Three-level Cache)解决构造器以外的循环依赖:
1. 一级缓存singletonObjects — 完全初始化的单例 Bean
2. 二级缓存earlySingletonObjects — 提前暴露的半成品 Bean(已实例化但未完成属性填充和初始化)
3. 三级缓存singletonFactories — 创建 Bean 的工厂(用于 AOP 场景)
Setter 注入支持循环依赖;构造器注入不支持循环依赖。

Q3:为什么推荐构造器注入?
① Bean 创建时即获得所有依赖,保证运行时依赖一定不为 null;② 依赖可用 final 修饰,保证不可变;③ 单元测试时不需要 Spring 容器,直接 new 即可;④ 可发现循环依赖问题(启动即报错,而非运行时)。

Q4:ApplicationContext 的预加载有什么缺点?
启动时间较长(所有单例 Bean 在启动时创建);占用更多内存(部分可能不常用的 Bean 也被提前创建)。但优点是提前发现配置错误运行时响应更快

>

SpringMVC 执行流程:DispatcherServlet / HandlerMapping / HandlerAdapter 完整解析

SpringMVC 执行流程:DispatcherServlet / HandlerMapping / HandlerAdapter 完整解析

一、定义

Spring MVC 基于前端控制器模式(Front Controller Pattern),所有请求通过中央控制器(DispatcherServlet)统一分发,再委派给具体的处理器处理。理解其完整执行流程是掌握 Spring MVC 的核心。

二、核心组件总览

组件 说明 默认实现
DispatcherServlet 前端控制器,统一接收请求并分发 FrameworkServlet
HandlerMapping 处理器映射器,根据请求找到对应 Handler RequestMappingHandlerMapping
HandlerAdapter 处理器适配器,调用具体 Handler RequestMappingHandlerAdapter
Handler(Controller) 处理器,处理具体业务逻辑 @Controller/@RestController
ModelAndView 封装模型数据和视图信息
ViewResolver 视图解析器,将逻辑视图名解析为具体视图 InternalResourceViewResolver
View 视图,渲染响应结果 JstlView, ThymeleafView
HandlerExceptionResolver 异常解析器 DefaultHandlerExceptionResolver

三、Spring MVC 完整执行流程

sequenceDiagram
    participant C as 客户端
    participant DS as DispatcherServlet
    participant HM as HandlerMapping
    participant HI as HandlerInterceptor
    participant HA as HandlerAdapter
    participant CT as Controller
    participant VR as ViewResolver
    participant V as View

    C->>DS: 发送请求
    DS->>HM: 1. 查找Handler
    HM->>DS: 返回HandlerExecutionChain

    DS->>HA: 2. 获取HandlerAdapter
    HA->>DS: 返回HandlerAdapter

    HA->>HI: 3. preHandle 拦截器前置处理
    HI-->>HA: 返回true继续

    HA->>CT: 4. 调用Controller方法
    CT->>CT: 执行业务逻辑
    CT->>HA: 返回ModelAndView/响应

    HA->>HI: 5. postHandle 拦截器后置处理

    DS->>VR: 6. 视图解析
    VR->>DS: 返回View对象

    DS->>V: 7. 视图渲染
    V->>C: 响应HTML/JSON

    DS->>HI: 8. afterCompletion 完成处理

四、详细步骤说明

步骤 1:DispatcherServlet 接收请求

// Spring Boot 自动配置 DispatcherServlet,以 / 拦截所有请求
// 传统 web.xml 配置:
<servlet>
    <servlet-name>dispatcherservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
servlet>
<servlet-mapping>
    <servlet-name>dispatcherservlet-name>
    <url-pattern>/url-pattern>
servlet-mapping>

步骤 2:HandlerMapping 查找处理器

// RequestMappingHandlerMapping 通过 @RequestMapping 匹配
@RestController
public class UserController {

    @GetMapping("/api/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}

步骤 3:HandlerInterceptor 前置处理

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            response.setStatus(401);
            return false; // 拦截请求
        }
        return true; // 放行
    }
}

步骤 4:HandlerAdapter 执行处理器

HandlerAdapter 的核心工作包括参数解析返回值处理

// 参数解析器(ArgumentResolver)处理:
// @PathVariable("id") → uriTemplateVariables.get("id")
// @RequestBody → HttpMessageConverter.read()
// @RequestParam → request.getParameter("name")
// @ModelAttribute → 对象属性绑定

// 返回值处理器(ReturnValueHandler)处理:
// @ResponseBody → RequestResponseBodyMethodProcessor → JSON
// ModelAndView → 视图解析渲染
// String → 逻辑视图名

步骤 5:Controller 执行业务逻辑

@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User saved = userService.save(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(saved);
    }
}

步骤 6:视图解析(传统 MVC)

@Controller
public class PageController {

    @GetMapping("/user/{id}")
    public String userDetail(@PathVariable Long id, Model model) {
        model.addAttribute("user", userService.findById(id));
        return "user/detail"; // 逻辑视图名 → ViewResolver 解析
    }
}

// ViewResolver 配置:
// prefix: /WEB-INF/views/
// suffix: .jsp
// 解析为:/WEB-INF/views/user/detail.jsp

步骤 7:响应输出

// REST 风格:@ResponseBody → HttpMessageConverter
// @ResponseBody → RequestResponseBodyMethodProcessor
// → 遍历 HttpMessageConverter
// → MappingJackson2HttpMessageConverter 将对象转为 JSON
// → 写入 Response

五、异常处理流程

flowchart TD
    A[Controller抛出异常] --> B{HandlerExceptionResolver链}
    B --> C[@ExceptionHandler匹配?]
    C -->|| D[自定义异常处理]
    C -->|| E[DefaultHandlerExceptionResolver
处理标准异常
] E --> F[ResponseStatusExceptionResolver
处理@ResponseStatus
] F --> G[SimpleMappingExceptionResolver
映射到错误视图]
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        return ResponseEntity.notFound().build();
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
        return ResponseEntity.internalServerError().body(
            new ErrorResponse("服务器内部错误"));
    }
}

六、要点总结

阶段 组件 职责
请求入口 DispatcherServlet 统一接收并分发请求
路由匹配 HandlerMapping 根据 URL 找 Handler
拦截过滤 HandlerInterceptor 前置/后置/完成处理
方法调用 HandlerAdapter 参数解析 + 方法执行 + 返回值处理
视图解析 ViewResolver 逻辑视图名 → 物理视图
异常处理 HandlerExceptionResolver 统一异常处理

七、面试常见问题

Q1:DispatcherServlet 在 Spring Boot 中怎么配置的?
Spring Boot 通过 WebMvcAutoConfiguration 自动配置 DispatcherServlet,以 / 拦截所有请求。不需要 web.xml。

Q2:HandlerMapping 和 HandlerAdapter 的区别?
HandlerMapping 负责找到能处理请求的 Handler(根据 URL 映射),HandlerAdapter 负责调用 Handler(适配不同调用方式,处理参数解析和返回值)。

Q3:Spring MVC 如何处理 @ResponseBody?
RequestMappingHandlerAdapter 检测方法是否有 @ResponseBody 注解,使用 RequestResponseBodyMethodProcessor 通过 HttpMessageConverter 将返回值转为 JSON/XML 写入响应。

Q4:Interceptor 和 Filter 的区别?
Filter 是 Servlet 规范的一部分,在 DispatcherServlet 之前执行;Interceptor 是 Spring MVC 组件,在 Handler 执行前后执行。Filter 作用范围更广,Interceptor 可以访问 Spring 上下文。


相关文章Spring Boot 自动配置原理 | @SpringBootApplication 注解详解 | AOP 原理与动态代理


Spring 事务传播机制:七种传播行为详解与最佳实践

Spring 事务传播机制:七种传播行为详解与最佳实践

一、定义

事务传播机制(Transaction Propagation)定义了当多个事务方法互相调用时,事务如何在这两个方法之间传播。Spring 通过 @Transactional(propagation=...) 来指定传播行为,它解决的是事务边界的问题——即多个声明了事务的方法嵌套调用时,是共享一个事务还是各自独立事务。

二、核心原理分析

Spring 事务传播机制基于 AOP 代理实现。当调用带有 @Transactional 的方法时,Spring 通过代理对象拦截方法调用,根据 Propagation 配置决定事务的创建、挂起或加入策略。

flowchart TD
    A[客户端调用] --> B{是否存在事务?}
    B -->|| C[REQUIRED/REQUIRES_NEW/NESTED]
    C --> D[创建新事务]
    B -->|| E{传播行为?}
    E -->|REQUIRED| F[加入当前事务]
    E -->|REQUIRES_NEW| G[挂起当前事务<br>创建新事务]
    E -->|NESTED| H[创建Savepoint<br>嵌套事务]
    E -->|SUPPORTS| I[加入当前事务]
    E -->|MANDATORY| J[加入当前事务]
    E -->|NOT_SUPPORTED| K[挂起当前事务<br>非事务执行]
    E -->|NEVER| L[抛出异常]
    B -->|| M{传播行为?}
    M -->|MANDATORY| N[抛出异常<br>IllegalTransactionStateException]
    M -->|NEVER| O[非事务方式执行]
    M -->|SUPPORTS/NOT_SUPPORTED| P[非事务方式执行]

三、七种传播行为详解

1. REQUIRED(默认传播行为)

定义:如果当前存在事务,则加入该事务;如果没有,则创建新事务。这是最常用的传播行为。

@Service
public class OrderService {

    @Autowired
    private AccountService accountService;

    @Transactional(propagation = Propagation.REQUIRED) // 默认值,可省略
    public void createOrderAndPay(Order order) {
        orderDao.save(order);
        accountService.deduct(order.getAmount()); // 加入同一个事务
    }
}

@Service
public class AccountService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void deduct(BigDecimal amount) {
        accountDao.deduct(amount);
    }
}

效果createOrderAndPaydeduct 在同一个事务中,任何一方抛异常都会导致全部回滚。

2. REQUIRES_NEW

定义:总是创建一个新事务。如果当前存在事务,则将当前事务挂起,等新事务执行完毕后再恢复。

@Service
public class OrderService {

    @Autowired
    private LogService logService;

    @Transactional
    public void createOrder() {
        orderDao.save(order);
        logService.recordLog("创建订单"); // 独立事务,即使外部回滚也不受影响
    }
}

@Service
public class LogService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void recordLog(String message) {
        logDao.save(new Log(message)); // 完全独立的事务
    }
}

适用场景:日志记录、审计、异步记录操作历史——即使主事务回滚,日志也必须持久化保存。

3. NESTED

定义:如果当前存在事务,则在嵌套事务中执行(使用 JDBC Savepoint);如果没有,则按 REQUIRED 执行。

@Service
public class BatchService {

    @Autowired
    private ItemService itemService;

    @Transactional
    public void processBatch(List<Item> items) {
        for (Item item : items) {
            try {
                itemService.processItem(item); // 每个 item 独立 Savepoint
            } catch (Exception e) {
                // 回滚单个 item,不影响 batch 中的其他 item
                log.error("Item failed: " + item.getId());
            }
        }
    }
}

@Service
public class ItemService {

    @Transactional(propagation = Propagation.NESTED)
    public void processItem(Item item) {
        // 如果抛异常,只回滚到当前 Savepoint
    }
}

4. SUPPORTS

支持当前事务,如果没有则以非事务方式执行。适用于查询方法——有事务就参与,没有也不强求。

@Transactional(propagation = Propagation.SUPPORTS)
public void queryData() {
    // 有事务则参与,没有则以非事务方式执行
}

5. MANDATORY

必须在已有事务中调用,否则抛出 IllegalTransactionStateException

@Transactional(propagation = Propagation.MANDATORY)
public void updateData() {
    // 必须在已有事务中调用,否则抛异常
}

6. NOT_SUPPORTED

以非事务方式执行,如果当前存在事务则挂起。

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void executeNonTransactional() {
    // 以非事务方式执行,如果有事务则挂起
}

7. NEVER

如果存在事务则抛出异常。

@Transactional(propagation = Propagation.NEVER)
public void execute() {
    // 如果存在事务则抛异常
}

四、REQUIRES_NEW vs NESTED 要点总结

对比维度 REQUIRES_NEW NESTED
原理 暂停当前事务,开启全新独立事务 基于 Savepoint,回滚到 Savepoint
事务独立性 完全独立(互不影响) 部分独立(外层可控制内层)
提交时机 内层事务独立提交 内层事务跟随外层提交
性能 开销大(两阶段提交) 开销小(Savepoint 机制)
JDBC 支持 所有数据库都支持 需要 JDBC 3.0 Savepoint 支持
内层回滚影响 不影响外层 仅回滚到 Savepoint

五、使用注意事项

自调用问题——事务传播不生效

Spring 事务基于 AOP 代理,自调用时 this.method() 不经过代理,@Transactional 注解不生效:

@Service
public class OrderService {

    @Transactional
    public void outerMethod() {
        innerMethod(); // ❌ 自调用,内部事务注解不生效
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void innerMethod() {
        // REQUIRES_NEW 不会生效,会加入 outerMethod 的事务
    }
}

解决方案:注入自身代理或拆分到不同的 Service。

六、面试常见问题

Q1:REQUIRED 和 REQUIRES_NEW 的区别?
REQUIRED 加入当前事务(共用一个),REQUIRES_NEW 挂起当前事务并创建全新独立事务。前者内层回滚整体回滚,后者内层回滚完全不影响外层。

Q2:REQUIRES_NEW 和 NESTED 的区别?
REQUIRES_NEW 是完全独立的事务(两阶段提交),NESTED 是基于 Savepoint 的嵌套事务。NESTED 的内层回滚只会回滚到 Savepoint,外层可以选择提交或回滚。

Q3:为什么自调用时 @Transactional 不生效?
AOP 代理只能在从外部通过代理对象调用时才起作用。类内部的 this.method() 调用直接绕过代理,事务注解不会被处理。

Q4:NESTED 需要什么条件?
需要 JDBC 3.0 的 Savepoint 支持。大多数主流数据库都支持,但某些数据库可能不支持。

七、传播机制选择指南

场景 推荐传播行为
大部分业务操作 REQUIRED
日志、审计、异步记录 REQUIRES_NEW
批量处理,单个失败不影响整体 NESTED
只读查询 SUPPORTS 或 REQUIRED
必须带事务调用的方法 MANDATORY
明确不允许在事务中执行 NEVER

相关文章Spring 事务隔离级别详解 | Spring AOP 原理与动态代理 | Spring Bean 生命周期


MyBatis 源码解析——从 JDBC 到 ORM 的封装艺术

一、引言:从 JDBC 到 MyBatis

Java 开发者早期的数据库操作记忆可以用一个词概括:重复性劳动。每次数据库操作都需要:获取连接 → 创建 Statement → 设置参数 → 执行 SQL → 处理 ResultSet 映射为对象 → 关闭连接。这只是最基本的 CRUD 操作,就已经需要十几行样板代码。

// JDBC 的原始写法——每个方法都要重复的样板代码
public class UserJdbcDao {

    public User findById(Long id) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 1. 获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", 
                                                 "user", "password");

            // 2. 创建 PreparedStatement,设置参数
            ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?");
            ps.setLong(1, id);

            // 3. 执行 SQL
            rs = ps.executeQuery();

            // 4. 处理结果集,手动映射为对象
            User user = null;
            if (rs.next()) {
                user = new User();
                user.setId(rs.getLong("id"));
                user.setName(rs.getString("name"));
                user.setEmail(rs.getString("email"));
                // 如果表有 20 列,这里就要写 20 行
            }
            return user;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            // 5. 关闭资源(最容易忘记的一步)
            try { if (rs != null) rs.close(); } catch (Exception e) {}
            try { if (ps != null) ps.close(); } catch (Exception e) {}
            try { if (conn != null) conn.close(); } catch (Exception e) {}
        }
    }

    // 每个方法都要重复上述整个流程!
}

MyBatis 解决了这个问题,但不是简单地封装了 JDBC。它的核心价值在于将 SQL 的灵活性ORM 的便捷性 结合在了一起。

flowchart LR
    JDBC[ JDBC\n灵活但代码冗长] -->|MyBatis\n封装了这层| SEMI[SQL 写在 XML\n映射交给框架]
    Hibernate[全自动 ORM\nSQL 由框架生成] -->| SQL 调优困难| SEMI
    SEMI -->|MyBatis 的定位| M[半自动 ORM\nSQL 可控\n映射自动]

本文将从 MyBatis 的源码出发,深入拆解 MyBatis 是如何封装 JDBC 的——从配置加载、SQL 解析、参数绑定、结果集映射到核心执行器。

二、MyBatis 核心架构总览

2.1 整体分层

flowchart TD
    subgraph 接口层
        API[SqlSession\n核心 API]
        API2[SqlSessionFactory\n工厂]
    end

    subgraph 核心处理层
        C1[Configuration\n全局配置]
        MAP[MapperRegistry\n映射器注册]
        SQL[SqlSource\nSQL 语句封装]
        PA[ParameterHandler\n参数处理器]
        RS[ResultSetHandler\n结果集处理器]
        STMT[StatementHandler\nStatement 处理器]
        EXEC[Executor\n执行器]
    end

    subgraph 基础支持层
        D1[数据源模块]
        D2[事务管理]
        D3[缓存模块\n一级/二级]
        D4[TypeHandler\n类型处理器]
        D5[日志模块]
        D6[反射工具箱\nMetaObject]
    end

    subgraph JDBC
        JDBC_PKG[java.sql.*]
    end

    API --> EXEC
    EXEC --> STMT
    STMT --> PA
    STMT --> RS
    EXEC --> D3
    STMT --> JDBC_PKG

2.2 一次 SQL 查询的流转过程

// 一个典型的 MyBatis 查询
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findById(1L);

// 内部流转:
// 1. mapper.findById(1L) → MapperProxy.invoke()
// 2. → MapperMethod.execute()
// 3. → SqlSession.selectOne()
// 4. → Executor.query()
// 5. → StatementHandler.query()
// 6. → ParameterHandler.setParameters()
// 7. → PreparedStatement.executeQuery()
// 8. → ResultSetHandler.handleResultSets()
// 9. → 返回 User 对象

三、配置加载——SqlSessionFactoryBuilder

3.1 XMLConfigBuilder——配置文件的解析

MyBatis 启动时,SqlSessionFactoryBuilder 使用 XMLConfigBuilder 解析配置文件:

public class XMLConfigBuilder extends BaseBuilder {

    // 解析 MyBatis 全局配置文件的入口
    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;

        // 从 XML 根节点  开始,按顺序解析子元素:
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

    // 配置元素解析顺序(必须遵循这个顺序,否则报错)
    private void parseConfiguration(XNode root) {
        try {
            // 1. 解析 (外部属性文件)
            propertiesElement(root.evalNode("properties"));
            // 2. 解析 (全局配置参数)
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 3. 解析 
            loadCustomVfs(settings);
            loadCustomLogImpl(settings);
            // 4. 解析 (类型别名)
            typeAliasesElement(root.evalNode("typeAliases"));
            // 5. 解析 (拦截器插件)
            pluginElement(root.evalNode("plugins"));
            // 6. 解析 
            objectFactoryElement(root.evalNode("objectFactory"));
            // 7. 解析 
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 8. 解析 
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 9. 解析  应用到 Configuration
            settingsElement(settings);
            // 10. 解析 (数据源 + 事务管理器)
            environmentsElement(root.evalNode("environments"));
            // 11. 解析 
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 12. 解析 (自定义类型处理器)
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 13. 解析 (映射器文件)
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration.", e);
        }
    }
}

元素解析顺序的重要性: MyBatis 要求配置文件中各元素的书写顺序必须与 parseConfiguration 方法中的解析顺序一致。比如, 必须在 之前,因为 environments 可能引用 properties 中的变量。

四、Mapper 注册与代理

4.1 MapperRegistry——注册 Mapper 接口

public class MapperRegistry {

    private final Configuration config;
    // Mapper 接口 → MapperProxyFactory 的映射
    private final Map<Class, MapperProxyFactory> knownMappers = new HashMap<>();

    // 注册 Mapper 接口
    public <T> void addMapper(Class<T> type) {
        // 检查:必须是接口
        if (type.isInterface()) {
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // 创建 MapperProxyFactory
                knownMappers.put(type, new MapperProxyFactory<>(type));

                // 解析 Mapper 接口上的注解(@Options 等)
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
                    type.getResourceAsStream(type.getName().replace('.', '/') + ".xml"),
                    config, type.getName(), config.getSqlFragments());
                xmlMapperBuilder.parse();

                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
}

4.2 MapperProxy——JDK 动态代理的实现

// 为什么 Mapper 接口不需要实现类?
// 答案:通过 JDK 动态代理
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> methodCache;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果是 Object 类的方法,直接调用
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            // 处理默认方法(JDK 8+)
            if (method.isDefault()) {
                return invokeDefaultMethod(proxy, method, args);
            }

            // 核心:通过缓存获取 MapperMethodInvoker
            // 每个 MapperMethod 对应一个 SQL 操作
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
}

动态代理的组成:

flowchart LR
    subgraph 应用层
        M[UserMapper 接口] -->|getMapper 返回代理对象| MP[MapperProxy]
    end

    subgraph MyBatis 内部
        MP -->|invoke 调起| MM[MapperMethod]
        MM -->|execute| SS[SqlSession]
        SS -->|selectOne / selectList| EX[Executor]
    end

    subgraph JDBC 
        EX --> ST[StatementHandler]
        ST -->|PreparedStatement| DB[(Database)]
        DB --> RS[ResultSet]
        RS -->|handleResultSets| MM
        MM -->|返回结果| MP
        MP -->|返回| M
    end

五、SQL 解析——从 XML 到 SqlSource

5.1 XMLStatementBuilder——解析 Mapper XML

public class XMLStatementBuilder extends BaseBuilder {

    // 解析  节点
    public void parseStatementNode() {
        // 提取 SQL 节点的各个属性
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
        String parameterType = context.getStringAttribute("parameterType");
        String resultType = context.getStringAttribute("resultType");
        String resultMap = context.getStringAttribute("resultMap");
        String flushCache = context.getStringAttribute("flushCache");
        String useCache = context.getStringAttribute("useCache");

        // SQL 语句解析(核心)
        // 使用 XNodeParser 将包含 ${}、#{}、 的 SQL 文本
        // 解析为 SqlSource 对象
        SqlSource sqlSource = langDriver.createSqlSource(configuration, 
            context, parameterTypeClass);

        // 构建 MappedStatement(一个 MappedStatement 对应一个 Mapper 方法)
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(
            configuration, id, sqlSource, sqlCommandType);
        // ... 设置各种属性
    }
}

5.2 动态 SQL 的解析——OGNL 的魔法

MyBatis 的动态 SQL 是其最强功能之一。 等标签的解析通过 OGNL(Object-Graph Navigation Language) 来实现:

// 动态 SQL 的处理节点
public class IfSqlNode implements SqlNode {
    private final ExpressionEvaluator evaluator;
    private final String test;       // OGNL 表达式
    private final SqlNode contents;  // 子节点

    public IfSqlNode(SqlNode contents, String test) {
        this.test = test;
        this.contents = contents;
        this.evaluator = new ExpressionEvaluator();
    }

    @Override
    public boolean apply(DynamicContext context) {
        // 评估 OGNL 表达式
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            contents.apply(context);  // 条件满足 → 拼接 SQL
            return true;
        }
        return false;
    }
}

//  标签的实现
public class WhereSqlNode implements SqlNode {
    // 自动处理 WHERE 关键字和 AND/OR 前缀
    // 如果子节点以 AND 或 OR 开头,自动移除

    @Override
    public boolean apply(DynamicContext context) {
        // 1. 让子节点拼接 SQL
        contents.apply(context);
        // 2. 检查拼接结果:开头不能是 AND 或 OR
        // 3. 如果没有子节点有内容,就不加 WHERE
        // 4. 否则在前面加上 WHERE
    }
}

一个动态 SQL 的解析示例:

 id="findByCondition" resultType="User">
    SELECT * FROM user
    
         test="name != null">
            AND name = #{name}
        
         test="email != null">
            AND email = #{email}
        
    







六、参数处理——ParameterHandler

6.1 参数绑定的过程

// 参数绑定的核心——将用户传入的参数设置到 PreparedStatement
public class DefaultParameterHandler implements ParameterHandler {

    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;

    @Override
    public void setParameters(PreparedStatement ps) {
        // 遍历 SQL 中的参数占位符
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);

                // 获取参数值
                Object value;
                if (parameterMapping.getMode() == ParameterMode.OUT) {
                    // 存储过程输出参数
                    value = null;
                } else {
                    // 普通参数:通过 MetaObject 获取值
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(parameterMapping.getProperty());
                }

                // 获取类型处理器
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();

                // 设置参数到 PreparedStatement
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
            }
        }
    }
}

6.2 TypeHandler——类型转换的核心

// TypeHandler 接口——各种类型转换的抽象
public interface TypeHandler<T> {
    // Java → JDBC(写入数据库时)
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) 
        throws SQLException;

    // JDBC → Java(从数据库读取时,通过列名)
    T getResult(ResultSet rs, String columnName) throws SQLException;

    // JDBC → Java(通过列索引)
    T getResult(ResultSet rs, int columnIndex) throws SQLException;
}

// 例如:StringTypeHandler
public class StringTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, 
                                     JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter);
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getString(columnName);
    }
}

// 自定义枚举 TypeHandler
public class GenderTypeHandler extends BaseTypeHandler<Gender> {
    // 将 Gender 枚举转为数据库的 VARCHAR
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Gender parameter, 
                                     JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.getCode());
    }

    @Override
    public Gender getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return Gender.fromCode(rs.getString(columnName));
    }
}

七、执行器(Executor)与缓存

7.1 执行器层级

flowchart TD
    subgraph Executor 层级
        BASE[BaseExecutor\n抽象基类]
        SIMPLE[SimpleExecutor\n每次执行创建 Statement]
        REUSE[ReuseExecutor\n缓存 Statement 复用]
        BATCH[BatchExecutor\n批量执行]
        CACHE[CachingExecutor\n二级缓存装饰器]
    end

    BASE --> SIMPLE
    BASE --> REUSE
    BASE --> BATCH
    CACHE -->|装饰| BASE

    style CACHE fill:#27ae60,color:#fff

7.2 一级缓存

public abstract class BaseExecutor implements Executor {

    // 一级缓存:PerpetualCache(HashMap)
    protected PerpetualCache localCache;

    // 查询逻辑
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, 
                              RowBounds rowBounds, ResultHandler resultHandler,
                              CacheKey key, BoundSql boundSql) throws SQLException {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }

        // 清空一级缓存的时机:flushCache=true 或执行了 update 操作
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }

        List<E> list;
        try {
            queryStack++;

            // 从一级缓存中查找
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;

            if (list != null) {
                // 缓存命中
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 缓存未命中 → 从数据库查询
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            queryStack--;
        }

        if (queryStack == 0) {
            // 延迟加载的处理
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            deferredLoads.clear();

            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // STATEMENT 级别:查询完成后立即清除缓存
                clearLocalCache();
            }
        }

        return list;
    }

    // 一级缓存的生命周期:
    // 1. 同一个 SqlSession 中,同一个 SQL + 参数,第二次查询直接返回缓存
    // 2. 执行 UPDATE/INSERT/DELETE 后,清除缓存
    // 3. SqlSession close 或 commit 后,清除缓存
    // 4. 跨 SqlSession 不共享(一级缓存是 SqlSession 级别的)
}

7.3 二级缓存

// CachingExecutor 通过装饰器模式为 Executor 添加二级缓存
public class CachingExecutor implements Executor {

    private final Executor delegate;  // 被装饰的 Executor
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, 
                              RowBounds rowBounds, ResultHandler resultHandler,
                              CacheKey key, BoundSql boundSql) throws SQLException {

        // 获取 MappedStatement 关联的二级缓存
        Cache cache = ms.getCache();

        if (cache != null) {
            // 先尝试从二级缓存获取
            List<E> list = tcm.getObject(cache, key);

            if (list == null) {
                // 二级缓存未命中 → 委派给底层 Executor 查询
                list = delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);

                // 查询结果放入二级缓存
                tcm.putObject(cache, key, list);
            }

            return list;
        }

        // 没有二级缓存 → 直接查询
        return delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
}
缓存 级别 生命周期 默认是否开启 适用范围
一级缓存 SqlSession 级别 SqlSession 生命周期内 开启 同一个 SqlSession 内的重复查询
二级缓存 Mapper 级别(跨 SqlSession) 整个应用运行期间 关闭 跨 SqlSession 的重复查询

八、结果集映射——ResultSetHandler

8.1 从 ResultSet 到 Java 对象

public class DefaultResultSetHandler implements ResultSetHandler {

    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        List<Object> multipleResults = new ArrayList<>();

        int resultSetCount = 0;
        ResultSetWrapper rsw = new ResultSetWrapper(stmt.getResultSet(), configuration);

        // 处理多结果集(存储过程等场景)
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        while (rsw != null && resultMapCount > resultSetCount) {
            ResultMap resultMap = resultMaps.get(resultSetCount);

            // 核心:将 ResultSet 当前行映射为 Java 对象
            handleResultSet(rsw, resultMap, multipleResults, null);

            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }

        // 处理 ResultSet 结果
        return collapseSingleResultList(multipleResults);
    }

    // 单行映射
    private Object handleRowValueForResultMap(ResultSetWrapper rsw, 
                                               ResultMap resultMap,
                                               CacheKey combinedKey,
                                               String columnPrefix,
                                               Object partialObject) throws SQLException {

        // 创建默认映射对象(通过无参构造器)
        Object resultObject = createResultObject(rsw, resultMap, constructor, columnPrefix);

        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            // 创建 MetaObject 用于设置属性
            MetaObject metaObject = configuration.newMetaObject(resultObject);

            // 遍历结果映射
            List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
            for (ResultMapping propertyMapping : propertyMappings) {
                // 处理嵌套映射(association、collection)
                if (propertyMapping.getNestedResultMapId() != null) {
                    handleRowValuesForNestedResultMap(rsw, resultMap, ...);
                } else {
                    // 普通列 → 属性映射
                    String columnName = propertyMapping.getColumn();
                    TypeHandler typeHandler = propertyMapping.getTypeHandler();
                    Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
                    metaObject.setValue(propertyMapping.getProperty(), value);
                }
            }
        }

        return resultObject;
    }
}

九、插件体系——拦截器的实现

MyBatis 的插件机制允许你在 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四个核心对象的方法调用过程中插入自定义逻辑。

// 插件本质上是通过 JDK 动态代理实现的拦截器

@Intercepts({
    @Signature(type = Executor.class, 
               method = "update",
               args = {MappedStatement.class, Object.class})
})
public class SqlMonitorPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();

        try {
            // 执行原方法
            return invocation.proceed();
        } finally {
            long duration = System.currentTimeMillis() - start;

            MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
            String sqlId = ms.getId();

            if (duration > 1000) {
                log.warn("Slow SQL [{}] took {}ms", sqlId, duration);
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        // 使用 Plugin.wrap 创建代理
        return Plugin.wrap(target, this);
    }
}

十、总结

  1. MyBatis 的核心是 JDBC 的优雅封装。 它将连接管理、参数设置、结果映射这些重复劳动抽象化,通过配置和注解实现了 SQL 与 Java 代码的分离。

  2. 动态代理是 Mapper 接口无需实现类的秘密。 MapperProxy 通过 JDK 动态代理将接口方法调用转换为 SqlSession 操作,这是整个 MyBatis-Spring 集成的基础。

  3. XML 解析是 MyBatis 的心脏。 从全局配置到 SQL 映射,XMLStatementBuilder 和 SqlSource 将动态 SQL 解析为可执行的 BoundSql,OGNL 表达式为动态 SQL 注入了灵活性。

  4. 缓存架构体现了”先读缓存,后读数据库”的思想。 一级缓存自动生效减少重复查询,二级缓存通过装饰器模式提供跨 SqlSession 的缓存共享,但需要谨慎使用(脏数据风险)。

  5. TypeHandler 是类型转换的桥梁。 MyBatis 内置了丰富的类型处理器,同时支持自定义 TypeHandler 以应对特殊枚举或复杂类型的转换需求。

  6. 插件体系是 MyBatis 扩展性的体现。 拦截四个核心对象的方法调用,可以无侵入地实现 SQL 监控、分页、性能统计等功能。


Spring Boot 核心机制源码级分析:IoC 容器、AOP 代理与事务管理


title: “Spring Boot 核心机制源码级分析:IoC 容器、AOP 代理与事务管理的底层实现”

一、引言

Spring Boot 的”开箱即用”背后,是一套精巧的核心机制在支撑:IoC(控制反转)容器管理着所有 Bean 的生命周期,依赖注入框架织就了组件间的协作网络,AOP 代理在运行时透明地增强方法,而事务管理则借助 AOP 实现了声明式的事务边界控制。本文将逐一切入 Spring Boot 的这些核心机制,深入源码进行剖析。

二、IoC 容器与 Bean 生命周期

2.1 容器初始化流程

Spring 容器的初始化始于 ApplicationContext 的创建。以 AnnotationConfigApplicationContext 为例:

// 入口代码
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = 
            new AnnotationConfigApplicationContext(AppConfig.class);
        // 此时容器已初始化完毕
    }
}

内部经历了三大步骤:

1. 扫描Scan
   └─ 找到所有 @Component   BeanDefinition 注册到 BeanFactory

2. 推断构建方法
   └─ 确定每个 Bean 使用哪个构造器

3. 实例化 + 初始化生命周期回调
flowchart TD
    A[创建 ApplicationContext] --> B[注册配置类 BeanDefinition]
    B --> C[调用 refresh 方法]
    C --> D[invokeBeanFactoryPostProcessors]
    D --> E[扫描包路径, 注册所有 BeanDefinition]
    E --> F[registerBeanPostProcessors]
    F --> G[finishBeanFactoryInitialization]
    G --> H[实例化所有非懒加载单例 Bean]

    D --> D1[BeanFactoryPostProcessor 处理 @Configuration]
    E --> E1[{
'beanDefinitions': 所有类都被扫描为 BeanDefinition
}
] H --> H1[调用 getBean → doGetBean → createBean]

2.2 Bean 生命周期完整流程

flowchart LR
    A[BeanDefinition] --> B[实例化 Instantiation]
    B --> C[属性赋值 Populate]
    C --> D[Aware 接口回调]
    D --> E[BeanPostProcessor before]
    E --> F[初始化方法 Init]
    F --> G[BeanPostProcessor after]
    G --> H[就绪 Ready]
    H --> I[销毁 Destroy]

    D --> D1[BeanNameAware / BeanFactoryAware<br/>ApplicationContextAware]
    E --> E1[@PostConstruct / InitializingBean]
    F --> F1[@Bean(initMethod)]
    G --> G1[AOP 代理创建点]
    I --> I1[@PreDestroy / DisposableBean]

2.3 三级缓存与循环依赖的解决

Spring 通过三级缓存解决构造器注入之外的循环依赖:

// DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry {
    // 一级缓存:完整初始化的单例 Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    // 二级缓存:早期暴露的 Bean(还未完全初始化)
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

    // 三级缓存:ObjectFactory 延迟生成代理
    private final Map<String, ObjectFactory> singletonFactories = new HashMap<>(16);
}
级别 缓存名称 内容 用途
一级 singletonObjects 完全初始化好的 Bean 最终成品
二级 earlySingletonObjects 实例化但未初始化的 Bean 暴露半成品
三级 singletonFactories 生成代理的工厂 延迟创建 AOP 代理

循环依赖解决示例:

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}

解决过程:

1. 创建 A实例化 A  A 放入三级缓存  属性注入 B
2. 创建 B实例化 B  B 放入三级缓存  属性注入 A
3. B 注入 A从三级缓存取 A  执行 ObjectFactory  放入二级缓存
4. B 完成初始化从二级缓存/或者finalAOP代理put一级
5. A 完成初始化从一级取B注入A完成put一级

注意: 构造器注入无法解决循环依赖,因为构造器调用在实例化之前,此时三级缓存还未放入工厂。会导致 BeanCurrentlyInCreationException

三、依赖注入实现原理

3.1 @Autowired 注入处理

AutowiredAnnotationBeanPostProcessor 负责处理 @Autowired@Value 注解:

class AutowiredAnnotationBeanPostProcessor {
    @Override
    public PropertyValues postProcessProperties(
            PropertyValues pvs, Object bean, String beanName) {

        InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass());
        metadata.inject(bean, beanName, pvs);
        return pvs;
    }
}

注入方式对比:

注入方式 处理时机 实现类 优缺点
@Autowired 字段 BeanPostProcessor 阶段 AutowiredAnnotationBeanPostProcessor 简洁,不可测试
setter 注入 BeanPostProcessor 阶段 AutowiredAnnotationBeanPostProcessor 可选依赖
构造器注入 实例化阶段显式初始化参数 AnnotationConfigUtils 推荐,不可变

构造器注入的实现:

// Spring 遍历所有构造器,选最优的
@Component
public class UserService {
    private final UserRepository userRepository;

    // Spring 自动选这个构造器(唯一或最"肥"的)
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

内部通过 ConstructorResolver.autowireConstructor() 方法解析,按参数类型和 @Qualifier 查找匹配的 Bean。

四、AOP 代理机制

4.1 代理创建流程

Spring AOP 的入口是 AbstractAutoProxyCreator(一个 BeanPostProcessor),在每个 Bean 初始化之后判断是否生成代理:

public abstract class AbstractAutoProxyCreator {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof AopInfrastructureBean) {
            return bean;
        }

        // 检查是否有 Advisor 匹配该 Bean
        Advisor[] advisors = getAdvicesAndAdvisorsForBean(
                bean.getClass(), beanName, null);

        if (advisors != null) {
            // 创建代理对象
            return createProxy(bean.getClass(), beanName, 
                    specificInterceptors, new SingletonTargetSource(bean));
        }
        return bean;
    }
}

4.2 JDK 动态代理 vs CGLIB

特性 JDK 动态代理 CGLIB
实现方式 基于接口,运行时生成 Proxy 类 基于子类,继承增强
要求 目标类必须实现接口 无要求
代理方法调用效率 反射 + Method.invoke FastClass 机制(索引调用)
无法代理的场景 final 类、final 方法、static 方法
Spring Boot 默认 2.0 之前 2.0 之后(需设置 proxyTargetClass=true

Spring Boot 2.0+ 默认使用 CGLIB(即使有接口),因为:

  1. 现代框架普遍基于类(@Configuration 类等),而不是接口
  2. CGLIB 的 FastClass 机制比 JDK 反射更高效
  3. 统一代理方式可避免接口与实现类不一致导致的诡异 BUG

4.3 拦截器链执行

sequenceDiagram
    participant Client as 调用方
    participant Proxy as AOP 代理对象
    participant Chain as 拦截器链
    participant Target as 目标方法

    Client->>Proxy: userService.findUser()
    Proxy->>Chain: 调用拦截器链
    Note over Chain: 第一个拦截器: @Around
    Chain->>Chain: 第二个拦截器: @Before
    Chain->>Target: proceed() 调用目标
    Chain->>Chain: 第三个拦截器: @AfterReturning
    Chain-->>Proxy: 返回结果
    Proxy-->>Client: 结果

    Note over Chain: 责任链模式
    Note over Chain: 顺序: Around  Before  目标  After  AfterReturning

4.4 代理自调用失效问题

@Service
public class UserService {

    @Transactional
    public void methodA() {
        methodB(); // 自调用:不会触发事务
    }

    @Transactional
    public void methodB() {
        // 数据库操作
    }
}

原因: methodA() 调用 methodB() 时,是通过 this.methodB() 调用的,this 是目标对象而不是代理对象,因此不会触发事务增强。

解决方案:

// 1. 注入自身代理(Spring 5+ 可用 @Lazy 解决循环依赖)
@Autowired
@Lazy
private UserService self;

public void methodA() {
    self.methodB(); // 通过代理调用
}

// 2. 提取到另一个 Service(推荐)
public void methodA() {
    auditService.methodB(); // 独立 Bean
}

五、事务管理底层

5.1 事务代理的创建

@EnableTransactionManagement 会向容器中注册 BeanFactoryTransactionAttributeSourceAdvisor,加上 TransactionInterceptor(实现了 MethodInterceptor),共同组成一个事务 Advisor。

当某个 Bean 有 @Transactional 方法时,InfrastructureAdvisorAutoProxyCreator 创建代理,事务拦截器被织入:

public class TransactionInterceptor extends TransactionAspectSupport 
        implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 获取事务属性
        TransactionAttribute txAttr = getTransactionAttributeSource()
                .getTransactionAttribute(invocation.getMethod(), targetClass);

        // 获取 PlatformTransactionManager
        PlatformTransactionManager tm = getTransactionManager();

        // 执行带事务的调用
        return invokeWithinTransaction(invocation.getMethod(), targetClass, 
                invocation::proceed, txAttr, tm);
    }
}

5.2 事务传播行为实现

声明式事务的传播行为(Propagation)是 Spring 区别于原生 JDBC 事务的核心能力:

传播行为 含义 实现逻辑
REQUIRED(默认) 支持当前事务,没有则新建 doGetTransaction() → 有则加入
REQUIRES_NEW 必须新建事务,挂起当前事务 挂起当前事务,创建新 Connection
SUPPORTS 有就用,没有就不开 简单判断是否已有事务
MANDATORY 必须要有事务,否则抛异常 NoTransactionException
NOT_SUPPORTED 必须以非事务方式执行 挂起当前事务
NESTED 嵌套事务(JDBC Savepoint) 创建数据库级保存点

REQUIRES_NEW 的实现:

@Override
protected Object doBeginTransaction(
        Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = 
        (DataSourceTransactionObject) transaction;

    Connection con = txObject.getConnectionHolder().getConnection();

    // REQUIRES_NEW:挂起当前事务,获取新连接
    if (txObject.hasTransaction()) {
        suspend(txObject);  // 保存当前事务状态
    }

    // 设置自动提交关闭、隔离级别等
    con.setAutoCommit(false);
    // ...
}

5.3 事务隔离级别的底层实现

flowchart TD
    subgraph "隔离级别与问题"
        READ_UNCOMMITTED -->|脏读+不可重复读+幻读| LOW[并发高, 一致性低]
        READ_COMMITTED -->|不可重复读+幻读| MID[PG/默认]
        REPEATABLE_READ -->|幻读| HIGH
        SERIALIZABLE -->|无并发问题| HIGHEST[并发极低]
    end

实现差异:

数据库 如何实现 REPEATABLE_READ
MySQL InnoDB MVCC + Next-Key Lock(行锁 + Gap Lock)
PostgreSQL MVCC(不会幻读,但允许更新冲突)
Oracle 只支持 READ_COMMITTED 和 SERIALIZABLE

六、总结

机制 核心组件 关键源码入口 常见陷阱
IoC 容器 DefaultListableBeanFactory refresh()finishBeanFactoryInitialization() 构造器循环依赖
DI 注入 AutowiredAnnotationBeanPostProcessor postProcessProperties() 字段注入无法测试
AOP 代理 AbstractAutoProxyCreator postProcessAfterInitialization() 自调用失效
事务管理 TransactionInterceptor invokeWithinTransaction() 自调用 + REQUIRES_NEW 误解

掌握 Spring Boot 的底层机制不仅仅是”看源码”,而是建立对框架行为模式的预期。当你理解了 BeanPostProcessor 的链式调用、AbstractAutoProxyCreator 的触发时机,以及事务 TransactionInterceptor 在代理链中的位置之后,绝大多数 Spring Boot 的”奇怪问题”都会变得清晰可预测。

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容