Java学习指南
  • Java 编程的逻辑
  • Java进阶
  • Java FrameWorks
  • 了解 USB Type-A,B,C 三大标准接口
  • 深入浅出DDD
  • 重构:改善既有代码的设计
  • 面试大纲
  • 云原生
    • 什么是无服务器(what is serverless)?
  • 博客
    • 深入分析Log4j 漏洞
  • 博客
    • Serverless之快速搭建Spring Boot应用
  • 博客
    • 使用 Prometheus + Grafana + Spring Boot Actuator 监控应用
  • 博客
    • 使用 Prometheus + Grafana 监控 MySQL
  • 博客
    • 使用Github Actions + Docker 部署Spring Boot应用
  • 博客
    • Redis分布式锁之Redisson的原理和实践
  • 博客
    • 数据库中的树结构应该怎样去设计
  • 学习&成长
    • 如何成为技术大牛
  • 开发工具
    • Git Commit Message Guidelines
  • 开发工具
    • git命名大全
  • 开发工具
    • Gradle vs Maven Comparison
  • 开发工具
    • Swagger2常用注解及其说明
  • 开发工具
    • 简明 VIM 练级攻略
  • 微服务
    • 十大微服务设计模式和原则
  • 微服务
    • 微服务下的身份认证和令牌管理
  • 微服务
    • 微服务坏味道之循环依赖
  • 设计模式
    • 设计模式 - JDK中的设计模式
  • 设计模式
    • 设计模式 - Java三种代理模式
  • 设计模式
    • 设计模式 - 六大设计原则
  • 设计模式
    • 设计模式 - 单例模式
  • 设计模式
    • 设计模式 - 命名模式
  • 设计模式
    • 设计模式 - 备忘录模式
  • 设计模式
    • 设计模式 - 概览
  • 设计模式
    • 设计模式 - 没用的设计模式
  • 质量&效率
    • Homebrew 替换国内镜像源
  • 质量&效率
    • 工作中如何做好技术积累
  • Java FrameWorks
    • Logback
      • 自定义 logback 日志过滤器
  • Java FrameWorks
    • Mybatis
      • MyBatis(十三) - 整合Spring
  • Java FrameWorks
    • Mybatis
      • MyBatis(十二) - 一些API
  • Java FrameWorks
    • Mybatis
      • Mybatis(一) - 概述
  • Java FrameWorks
    • Mybatis
      • Mybatis(七) - 结果集的封装与映射
  • Java FrameWorks
    • Mybatis
      • Mybatis(三) - mapper.xml及其加载机制
  • Java FrameWorks
    • Mybatis
      • Mybatis(九) - 事务
  • Java FrameWorks
    • Mybatis
      • Mybatis(二) - 全局配置文件及其加载机制
  • Java FrameWorks
    • Mybatis
      • Mybatis(五) - SqlSession执行流程
  • Java FrameWorks
    • Mybatis
      • Mybatis(八) - 缓存
  • Java FrameWorks
    • Mybatis
      • Mybatis(六) - 动态SQL的参数绑定与执行
  • Java FrameWorks
    • Mybatis
      • Mybatis(十) - 插件
  • Java FrameWorks
    • Mybatis
      • Mybatis(十一) - 日志
  • Java FrameWorks
    • Mybatis
      • Mybatis(四) - Mapper接口解析
  • Java FrameWorks
    • Netty
      • Netty 可靠性分析
  • Java FrameWorks
    • Netty
      • Netty - Netty 线程模型
  • Java FrameWorks
    • Netty
      • Netty堆外内存泄露排查盛宴
  • Java FrameWorks
    • Netty
      • Netty高级 - 高性能之道
  • Java FrameWorks
    • Shiro
      • Shiro + JWT + Spring Boot Restful 简易教程
  • Java FrameWorks
    • Shiro
      • 非常详尽的 Shiro 架构解析!
  • Java FrameWorks
    • Spring
      • Spring AOP 使用介绍,从前世到今生
  • Java FrameWorks
    • Spring
      • Spring AOP 源码解析
  • Java FrameWorks
    • Spring
      • Spring Event 实现原理
  • Java FrameWorks
    • Spring
      • Spring Events
  • Java FrameWorks
    • Spring
      • Spring IOC容器源码分析
  • Java FrameWorks
    • Spring
      • Spring Integration简介
  • Java FrameWorks
    • Spring
      • Spring MVC 框架中拦截器 Interceptor 的使用方法
  • Java FrameWorks
    • Spring
      • Spring bean 解析、注册、实例化流程源码剖析
  • Java FrameWorks
    • Spring
      • Spring validation中@NotNull、@NotEmpty、@NotBlank的区别
  • Java FrameWorks
    • Spring
      • Spring 如何解决循环依赖?
  • Java FrameWorks
    • Spring
      • Spring 异步实现原理与实战分享
  • Java FrameWorks
    • Spring
      • Spring中的“for update”问题
  • Java FrameWorks
    • Spring
      • Spring中的设计模式
  • Java FrameWorks
    • Spring
      • Spring事务失效的 8 大原因
  • Java FrameWorks
    • Spring
      • Spring事务管理详解
  • Java FrameWorks
    • Spring
      • Spring计时器StopWatch使用
  • Java FrameWorks
    • Spring
      • 详述 Spring MVC 框架中拦截器 Interceptor 的使用方法
  • Java FrameWorks
    • Spring
      • 透彻的掌握 Spring 中@transactional 的使用
  • Java
    • Java IO&NIO&AIO
      • Java IO - BIO 详解
  • Java
    • Java IO&NIO&AIO
      • Java NIO - IO多路复用详解
  • Java
    • Java IO&NIO&AIO
      • Java N(A)IO - Netty
  • Java
    • Java IO&NIO&AIO
      • Java IO - Unix IO模型
  • Java
    • Java IO&NIO&AIO
      • Java IO - 分类
  • Java
    • Java IO&NIO&AIO
      • Java NIO - 基础详解
  • Java
    • Java IO&NIO&AIO
      • Java IO - 常见类使用
  • Java
    • Java IO&NIO&AIO
      • Java AIO - 异步IO详解
  • Java
    • Java IO&NIO&AIO
      • Java IO概述
  • Java
    • Java IO&NIO&AIO
      • Java IO - 设计模式
  • Java
    • Java IO&NIO&AIO
      • Java NIO - 零拷贝实现
  • Java
    • Java JVM
      • JVM 优化经验总结
  • Java
    • Java JVM
      • JVM 内存结构
  • Java
    • Java JVM
      • JVM参数设置
  • Java
    • Java JVM
      • Java 内存模型
  • Java
    • Java JVM
      • 从实际案例聊聊Java应用的GC优化
  • Java
    • Java JVM
      • Java 垃圾回收器G1详解
  • Java
    • Java JVM
      • 垃圾回收器Shenandoah GC详解
  • Java
    • Java JVM
      • 垃圾回收器ZGC详解
  • Java
    • Java JVM
      • 垃圾回收基础
  • Java
    • Java JVM
      • 如何优化Java GC
  • Java
    • Java JVM
      • 类加载机制
  • Java
    • Java JVM
      • 类字节码详解
  • Java
    • Java 基础
      • Java hashCode() 和 equals()
  • Java
    • Java 基础
      • Java 基础 - Java native方法以及JNI实践
  • Java
    • Java 基础
      • Java serialVersionUID 有什么作用?
  • Java
    • Java 基础
      • Java 泛型的类型擦除
  • Java
    • Java 基础
      • Java 基础 - Unsafe类解析
  • Java
    • Java 基础
      • Difference Between Statement and PreparedStatement
  • Java
    • Java 基础
      • Java 基础 - SPI机制详解
  • Java
    • Java 基础
      • Java 基础 - final
  • Java
    • Java 基础
      • Java中static关键字详解
  • Java
    • Java 基础
      • 为什么说Java中只有值传递?
  • Java
    • Java 基础
      • Java 基础 - 即时编译器原理解析及实践
  • Java
    • Java 基础
      • Java 基础 - 反射
  • Java
    • Java 基础
      • Java多态的面试题
  • Java
    • Java 基础
      • Java 基础 - 异常机制详解
  • Java
    • Java 基础
      • 为什么要有抽象类?
  • Java
    • Java 基础
      • 接口的本质
  • Java
    • Java 基础
      • Java 基础 - 枚举
  • Java
    • Java 基础
      • Java 基础 - 泛型机制详解
  • Java
    • Java 基础
      • Java 基础 - 注解机制详解
  • Java
    • Java 基础
      • 为什么 String hashCode 方法选择数字31作为乘子
  • Java
    • Java 并发
      • Java 并发 - 14个Java并发容器
  • Java
    • Java 并发
      • Java 并发 - AQS
  • Java
    • Java 并发
      • Java 并发 - BlockingQueue
  • Java
    • Java 并发
      • Java 并发 - CAS
  • Java
    • Java 并发
      • Java 并发 - Condition接口
  • Java
    • Java 并发
      • Java 并发 - CopyOnWriteArrayList
  • Java
    • Java 并发
      • Java 并发 - CountDownLatch、CyclicBarrier和Phaser对比
  • Java
    • Java 并发
      • Java 并发 - Fork&Join框架
  • Java
    • Java 并发
      • Java 并发 - Java CompletableFuture 详解
  • Java
    • Java 并发
      • Java 并发 - Java 线程池
  • Java
    • Java 并发
      • Java 并发 - Lock接口
  • Java
    • Java 并发
      • Java 并发 - ReentrantLock
  • Java
    • Java 并发
      • Java 并发 - ReentrantReadWriteLock
  • Java
    • Java 并发
      • Java 并发 - Synchronized
  • Java
    • Java 并发
      • Java 并发 - ThreadLocal 内存泄漏问题
  • Java
    • Java 并发
      • Java 并发 - ThreadLocal
  • Java
    • Java 并发
      • Java 并发 - Volatile
  • Java
    • Java 并发
      • Java 并发 - 从ReentrantLock的实现看AQS的原理及应用
  • Java
    • Java 并发
      • Java 并发 - 公平锁和非公平锁
  • Java
    • Java 并发
      • Java 并发 - 内存模型
  • Java
    • Java 并发
      • Java 并发 - 原子类
  • Java
    • Java 并发
      • Java 并发 - 如何确保三个线程顺序执行?
  • Java
    • Java 并发
      • Java 并发 - 锁
  • Java
    • Java 的新特性
      • Java 10 新特性概述
  • Java
    • Java 的新特性
      • Java 11 新特性概述
  • Java
    • Java 的新特性
      • Java 12 新特性概述
  • Java
    • Java 的新特性
      • Java 13 新特性概述
  • Java
    • Java 的新特性
      • Java 14 新特性概述
  • Java
    • Java 的新特性
      • Java 15 新特性概述
  • Java
    • Java 的新特性
      • Java 8的新特性
  • Java
    • Java 的新特性
      • Java 9 新特性概述
  • Java
    • Java 调试排错
      • 调试排错 - Java Debug Interface(JDI)详解
  • Java
    • Java 调试排错
      • 调试排错 - CPU 100% 排查优化实践
  • Java
    • Java 调试排错
      • 调试排错 - Java Heap Dump分析
  • Java
    • Java 调试排错
      • 调试排错 - Java Thread Dump分析
  • Java
    • Java 调试排错
      • 调试排错 - Java动态调试技术原理
  • Java
    • Java 调试排错
      • 调试排错 - Java应用在线调试Arthas
  • Java
    • Java 调试排错
      • 调试排错 - Java问题排查:工具单
  • Java
    • Java 调试排错
      • 调试排错 - 内存溢出与内存泄漏
  • Java
    • Java 调试排错
      • 调试排错 - 在线分析GC日志的网站GCeasy
  • Java
    • Java 调试排错
      • 调试排错 - 常见的GC问题分析与解决
  • Java
    • Java 集合
      • Java 集合 - ArrayList
  • Java
    • Java 集合
      • Java 集合 - HashMap 和 ConcurrentHashMap
  • Java
    • Java 集合
      • Java 集合 - HashMap的死循环问题
  • Java
    • Java 集合
      • Java 集合 - LinkedHashSet&Map
  • Java
    • Java 集合
      • Java 集合 - LinkedList
  • Java
    • Java 集合
      • Java 集合 - PriorityQueue
  • Java
    • Java 集合
      • Java 集合 - Stack & Queue
  • Java
    • Java 集合
      • Java 集合 - TreeSet & TreeMap
  • Java
    • Java 集合
      • Java 集合 - WeakHashMap
  • Java
    • Java 集合
      • Java 集合 - 为什么HashMap的容量是2的幂次方
  • Java
    • Java 集合
      • Java 集合 - 概览
  • Java
    • Java 集合
      • Java 集合 - 高性能队列Disruptor详解
  • 分布式
    • RPC
      • ⭐️RPC - Dubbo&hsf&Spring cloud的区别
  • 分布式
    • RPC
      • ⭐️RPC - Dubbo的架构原理
  • 分布式
    • RPC
      • ⭐️RPC - HSF的原理分析
  • 分布式
    • RPC
      • ⭐️RPC - 你应该知道的RPC原理
  • 分布式
    • RPC
      • ⭐️RPC - 动态代理
  • 分布式
    • RPC
      • 深入理解 RPC 之协议篇
  • 分布式
    • RPC
      • RPC - 序列化和反序列化
  • 分布式
    • RPC
      • ⭐️RPC - 服务注册与发现
  • 分布式
    • RPC
      • RPC - 核心原理
  • 分布式
    • RPC
      • ⭐️RPC - 框架对比
  • 分布式
    • RPC
      • ⭐️RPC - 网络通信
  • 分布式
    • 分布式事务
      • 分布式事务 Seata TCC 模式深度解析
  • 分布式
    • 分布式事务
      • 分布式事务的实现原理
  • 分布式
    • 分布式事务
      • 常用的分布式事务解决方案
  • 分布式
    • 分布式事务
      • 手写实现基于消息队列的分布式事务框架
  • 分布式
    • 分布式算法
      • CAP 定理的含义
  • 分布式
    • 分布式算法
      • Paxos和Raft比较
  • 分布式
    • 分布式算法
      • 分布式一致性与共识算法
  • 分布式
    • 分布式锁
      • ⭐️分布式锁的原理及实现方式
  • 分布式
    • 搜索引擎
      • ElasticSearch与SpringBoot的集成与JPA方法的使用
  • 分布式
    • 搜索引擎
      • 全文搜索引擎 Elasticsearch 入门教程
  • 分布式
    • 搜索引擎
      • 十分钟学会使用 Elasticsearch 优雅搭建自己的搜索系统
  • 分布式
    • 搜索引擎
      • 腾讯万亿级 Elasticsearch 技术解密
  • 分布式
    • 日志系统
      • Grafana Loki 简明教程
  • 分布式
    • 日志系统
      • 分布式系统中如何优雅地追踪日志
  • 分布式
    • 日志系统
      • 如何优雅地记录操作日志?
  • 分布式
    • 日志系统
      • 日志收集组件—Flume、Logstash、Filebeat对比
  • 分布式
    • 日志系统
      • 集中式日志系统 ELK 协议栈详解
  • 分布式
    • 消息队列
      • 消息队列 - Kafka
  • 分布式
    • 消息队列
      • 消息队列 - Kafka、RabbitMQ、RocketMQ等消息中间件的对比
  • 分布式
    • 消息队列
      • 消息队列之 RabbitMQ
  • 分布式
    • 消息队列
      • 消息队列 - 使用docker-compose构建kafka集群
  • 分布式
    • 消息队列
      • 消息队列 - 分布式系统与消息的投递
  • 分布式
    • 消息队列
      • 消息队列 - 如何保证消息的可靠性传输
  • 分布式
    • 消息队列
      • 消息队列 - 如何保证消息的顺序性
  • 分布式
    • 消息队列
      • 消息队列 - 如何保证消息队列的高可用
  • 分布式
    • 消息队列
      • 消息队列 - 消息队列设计精要
  • 分布式
    • 监控系统
      • 深度剖析开源分布式监控CAT
  • 大数据
    • Flink
      • Flink架构与核心组件
  • 微服务
    • Dubbo
      • 基于dubbo的分布式应用中的统一异常处理
  • 微服务
    • Dubbo
      • Vim快捷键
  • 微服务
    • Service Mesh
      • Istio 是什么?
  • 微服务
    • Service Mesh
      • OCTO 2.0:美团基于Service Mesh的服务治理系统详解
  • 微服务
    • Service Mesh
      • Service Mesh是什么?
  • 微服务
    • Service Mesh
      • Spring Cloud向Service Mesh迁移
  • 微服务
    • Service Mesh
      • 数据挖掘算法
  • 微服务
    • Service Mesh
      • Seata Saga 模式
  • 微服务
    • Spring Cloud
      • Seata TCC 模式
  • 微服务
    • Spring Cloud
      • Spring Cloud Config
  • 微服务
    • Spring Cloud
      • Seata AT 模式
  • 微服务
    • Spring Cloud
      • Spring Cloud Gateway
  • 微服务
    • Spring Cloud
      • Spring Cloud OpenFeign 的核心原理
  • 微服务
    • Spring Cloud
      • Seata XA 模式
  • 数据库
    • Database Version Control
      • Liquibase vs. Flyway
  • 数据库
    • Database Version Control
      • Six reasons to version control your database
  • 数据库
    • MySQL
      • How Sharding Works
  • 数据库
    • MySQL
      • MySQL InnoDB中各种SQL语句加锁分析
  • 数据库
    • MySQL
      • MySQL 事务隔离级别和锁
  • 数据库
    • MySQL
      • MySQL 索引性能分析概要
  • 数据库
    • MySQL
      • MySQL 索引设计概要
  • 数据库
    • MySQL
      • MySQL出现Waiting for table metadata lock的原因以及解决方法
  • 数据库
    • MySQL
      • MySQL的Limit性能问题
  • 数据库
    • MySQL
      • MySQL索引优化explain
  • 数据库
    • MySQL
      • MySQL索引背后的数据结构及算法原理
  • 数据库
    • MySQL
      • MySQL行转列、列转行问题
  • 数据库
    • MySQL
      • 一条SQL更新语句是如何执行的?
  • 数据库
    • MySQL
      • 一条SQL查询语句是如何执行的?
  • 数据库
    • MySQL
      • 为什么 MySQL 使用 B+ 树
  • 数据库
    • MySQL
      • 为什么 MySQL 的自增主键不单调也不连续
  • 数据库
    • MySQL
      • 为什么我的MySQL会“抖”一下?
  • 数据库
    • MySQL
      • 为什么数据库不应该使用外键
  • 数据库
    • MySQL
      • 为什么数据库会丢失数据
  • 数据库
    • MySQL
      • 事务的可重复读的能力是怎么实现的?
  • 数据库
    • MySQL
      • 大众点评订单系统分库分表实践
  • 数据库
    • MySQL
      • 如何保证缓存与数据库双写时的数据一致性?
  • 数据库
    • MySQL
      • 浅谈数据库并发控制 - 锁和 MVCC
  • 数据库
    • MySQL
      • 深入浅出MySQL 中事务的实现
  • 数据库
    • MySQL
      • 浅入浅出MySQL 和 InnoDB
  • 数据库
    • PostgreSQL
      • PostgreSQL upsert功能(insert on conflict do)的用法
  • 数据库
    • Redis
      • Redis GEO & 实现原理深度分析
  • 数据库
    • Redis
      • Redis 和 I/O 多路复用
  • 数据库
    • Redis
      • Redis分布式锁
  • 数据库
    • Redis
      • Redis实现分布式锁中的“坑”
  • 数据库
    • Redis
      • Redis总结
  • 数据库
    • Redis
      • 史上最全Redis高可用技术解决方案大全
  • 数据库
    • Redis
      • Redlock:Redis分布式锁最牛逼的实现
  • 数据库
    • Redis
      • 为什么 Redis 选择单线程模型
  • 数据库
    • TiDB
      • 新一代数据库TiDB在美团的实践
  • 数据库
    • 数据仓库
      • 实时数仓在有赞的实践
  • 数据库
    • 数据库原理
      • OLTP与OLAP的关系是什么?
  • 数据库
    • 数据库原理
      • 为什么 OLAP 需要列式存储
  • 系统设计
    • DDD
      • Domain Primitive
  • 系统设计
    • DDD
      • Repository模式
  • 系统设计
    • DDD
      • 应用架构
  • 系统设计
    • DDD
      • 聊聊如何避免写流水账代码
  • 系统设计
    • DDD
      • 领域层设计规范
  • 系统设计
    • DDD
      • 从三明治到六边形
  • 系统设计
    • DDD
      • 阿里盒马领域驱动设计实践
  • 系统设计
    • DDD
      • 领域驱动设计(DDD)编码实践
  • 系统设计
    • DDD
      • 领域驱动设计在互联网业务开发中的实践
  • 系统设计
    • 基础架构
      • 容错,高可用和灾备
  • 系统设计
    • 数据聚合
      • GraphQL及元数据驱动架构在后端BFF中的实践
  • 系统设计
    • 数据聚合
      • 高效研发-闲鱼在数据聚合上的探索与实践
  • 系统设计
    • 服务安全
      • JSON Web Token 入门教程
  • 系统设计
    • 服务安全
      • 你还在用JWT做身份认证嘛?
  • 系统设计
    • 服务安全
      • 凭证(Credentials)
  • 系统设计
    • 服务安全
      • 授权(Authorization)
  • 系统设计
    • 服务安全
      • 理解OAuth2.0
  • 系统设计
    • 服务安全
      • 认证(Authentication)
  • 系统设计
    • 架构案例
      • 微信 Android 客户端架构演进之路
  • 系统设计
    • 高可用架构
      • 业务高可用的保障:异地多活架构
  • 计算机基础
    • 字符编码
      • Base64原理解析
  • 计算机基础
    • 字符编码
      • 字符编码笔记:ASCII,Unicode 和 UTF-8
  • 计算机基础
    • 操作系统
      • 为什么 CPU 访问硬盘很慢
  • 计算机基础
    • 操作系统
      • 为什么 HTTPS 需要 7 次握手以及 9 倍时延
  • 计算机基础
    • 操作系统
      • 为什么 Linux 默认页大小是 4KB
  • 计算机基础
    • 操作系统
      • 磁盘IO那些事
  • 计算机基础
    • 操作系统
      • 虚拟机的3种网络模式
  • 计算机基础
    • 服务器
      • mac终端bash、zsh、oh-my-zsh最实用教程
  • 计算机基础
    • 服务器
      • Nginx强制跳转Https
  • 计算机基础
    • 服务器
      • curl 的用法指南
  • 计算机基础
    • 网络安全
      • 如何设计一个安全的对外接口?
  • 计算机基础
    • 网络安全
      • 浅谈常见的七种加密算法及实现
  • 计算机基础
    • 网络编程
      • MQTT - The Standard for IoT Messaging
  • 计算机基础
    • 网络编程
      • 两万字长文 50+ 张趣图带你领悟网络编程的内功心法
  • 计算机基础
    • 网络编程
      • 为什么 TCP 协议有 TIME_WAIT 状态
  • 计算机基础
    • 网络编程
      • 为什么 TCP 协议有性能问题
  • 计算机基础
    • 网络编程
      • 为什么 TCP 协议有粘包问题
  • 计算机基础
    • 网络编程
      • 为什么 TCP 建立连接需要三次握手
  • 计算机基础
    • 网络编程
      • 为什么 TCP/IP 协议会拆分数据
  • 计算机基础
    • 网络编程
      • 使用 OAuth 2 和 JWT 为微服务提供安全保障
  • 计算机基础
    • 网络编程
      • 四种常见的 POST 提交数据方式
  • 计算机基础
    • 网络编程
      • 有赞TCP网络编程最佳实践
  • 计算机基础
    • 网络编程
      • 看完这篇HTTP,跟面试官扯皮就没问题了
  • 计算机基础
    • 网络编程
      • 详细解析 HTTP 与 HTTPS 的区别
  • 质量&效率
    • 快捷键
      • Idea快捷键(Mac版)
  • 质量&效率
    • 快捷键
      • Shell快捷键
  • 质量&效率
    • 快捷键
      • conduit
  • 质量&效率
    • 敏捷开发
      • Scrum的3种角色
  • 质量&效率
    • 敏捷开发
      • Scrum的4种会议
  • 质量&效率
    • 敏捷开发
      • ThoughtWorks的敏捷开发
  • 质量&效率
    • 敏捷开发
      • 敏捷开发入门教程
  • 运维&测试
    • Docker
      • Docker (容器) 的原理
  • 运维&测试
    • Docker
      • Docker Compose:链接外部容器的几种方式
  • 运维&测试
    • Docker
      • Docker 入门教程
  • 运维&测试
    • Docker
      • Docker 核心技术与实现原理
  • 运维&测试
    • Docker
      • Dockerfile 最佳实践
  • 运维&测试
    • Docker
      • Docker开启Remote API 访问 2375端口
  • 运维&测试
    • Docker
      • Watchtower - 自动更新 Docker 镜像与容器
  • 运维&测试
    • Kubernetes
      • Kubernetes 介绍
  • 运维&测试
    • Kubernetes
      • Kubernetes 在有赞的实践
  • 运维&测试
    • Kubernetes
      • Kubernetes 学习路径
  • 运维&测试
    • Kubernetes
      • Kubernetes如何改变美团的云基础设施?
  • 运维&测试
    • Kubernetes
      • Kubernetes的三种外部访问方式:NodePort、LoadBalancer 和 Ingress
  • 运维&测试
    • Kubernetes
      • 谈 Kubernetes 的架构设计与实现原理
  • 运维&测试
    • 压测
      • 全链路压测平台(Quake)在美团中的实践
  • 运维&测试
    • 测试
      • Cpress - JavaScript End to End Testing Framework
  • 运维&测试
    • 测试
      • 代码覆盖率-JaCoCo
  • 运维&测试
    • 测试
      • 浅谈代码覆盖率
  • 运维&测试
    • 测试
      • 测试中 Fakes、Mocks 以及 Stubs 概念明晰
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP中的Bean是如何被AOP代理的
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP原生动态代理和Cglib动态代理
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP实现方式(xml&注解)
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP是如何收集切面类并封装的
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP概述
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP的底层核心后置处理器
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP的延伸知识
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(一)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(三)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(二)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(五)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(四) - 循环依赖与解决方案
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - 启动引导
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot JarLauncher
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot Web Mvc 自动装配
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot 使用ApplicationListener监听器
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot 声明式事务
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot 嵌入式容器
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot引起的“堆外内存泄漏”排查及经验总结
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot的启动流程
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot自动化配置源码分析
  • Java FrameWorks
    • Spring
      • Spring Boot
        • 如何自定义Spring Boot Starter?
  • Java FrameWorks
    • Spring
      • Spring IOC
        • IOC - 模块装配和条件装配
  • Java FrameWorks
    • Spring
      • Spring IOC
        • IOC - 配置源(xml,注解)
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Environment
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring ApplicationContext
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanDefinition
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanFactory
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanFactoryPostProcessor
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanPostProcessor
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(一) - 概述
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(三) - 实例化阶段
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(二) - BeanDefinition
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(五) - 销毁阶段
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(四) - 初始化阶段
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring ComponentScan
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Events
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC 基础篇
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC 总结
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC 进阶篇
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC容器的生命周期
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Resource
  • Java FrameWorks
    • Spring
      • Spring MVC
        • DispatcherServlet的初始化原理
  • Java FrameWorks
    • Spring
      • Spring MVC
        • DispatcherServlet的核心工作原理
  • Java FrameWorks
    • Spring
      • Spring MVC
        • WebMvc的架构设计与组件功能解析
  • Java FrameWorks
    • Spring
      • Spring Security
        • Spring Boot 2 + Spring Security 5 + JWT 的单页应用 Restful 解决方案
  • Java FrameWorks
    • Spring
      • Spring Security
        • Spring Security Oauth
  • Java FrameWorks
    • Spring
      • Spring Security
        • Spring Security
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • DispatcherHandler的工作原理(传统方式)
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • DispatcherHandler的工作原理(函数式端点)
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • WebFlux的自动装配
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • 快速了解响应式编程与Reactive
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • 快速使用WebFlux
  • 分布式
    • 协调服务
      • Zookeeper
        • Zookeeper - 客户端之 Curator
  • 分布式
    • 协调服务
      • Zookeeper
        • 详解分布式协调服务 ZooKeeper
  • 分布式
    • 协调服务
      • etcd
        • 高可用分布式存储 etcd 的实现原理
  • 数据库
    • Database Version Control
      • Flyway
        • Database Migrations with Flyway
  • 数据库
    • Database Version Control
      • Flyway
        • How Flyway works
  • 数据库
    • Database Version Control
      • Flyway
        • Rolling Back Migrations with Flyway
  • 数据库
    • Database Version Control
      • Flyway
        • The meaning of the concept of checksums
  • 数据库
    • Database Version Control
      • Liquibase
        • Introduction to Liquibase Rollback
  • 数据库
    • Database Version Control
      • Liquibase
        • LiquiBase中文学习指南
  • 数据库
    • Database Version Control
      • Liquibase
        • Use Liquibase to Safely Evolve Your Database Schema
  • 系统设计
    • 流量控制
      • RateLimiter
        • Guava Rate Limiter实现分析
  • 系统设计
    • 流量控制
      • Sentinel
        • Sentinel 与 Hystrix 的对比
  • 系统设计
    • 流量控制
      • Sentinel
        • Sentinel工作主流程
  • 系统设计
    • 流量控制
      • 算法
        • 分布式服务限流实战
  • 系统设计
    • 解决方案
      • 秒杀系统
        • 如何设计一个秒杀系统
  • 系统设计
    • 解决方案
      • 红包系统
        • 微信高并发资金交易系统设计方案--百亿红包背后的技术支撑
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 什么是预排序遍历树算法(MPTT,Modified Preorder Tree Traversal)
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 加密算法
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 推荐系统算法
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • linkerd
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 查找算法
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 缓存淘汰算法中的LRU和LFU
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 负载均衡算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - Paxos算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - Raft算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - Snowflake算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - ZAB算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - 一致性Hash算法
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - Bitmap & Bloom Filter
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - Map & Reduce
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - Trie树/数据库/倒排索引
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 分治/hash/排序
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 双层桶划分
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 外(磁盘文件)排序
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 布隆过滤器
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理算法
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 文本预处理:后缀树(Suffix Tree)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 模式预处理:BM 算法 (Boyer-Moore)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 模式预处理:KMP 算法(Knuth-Morris-Pratt)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 模式预处理:朴素算法(Naive)(暴力破解)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 分支限界算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 分治算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 动态规划算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 回溯算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 贪心算法
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 十大排序算法
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(一)之3种简单排序(选择,冒泡,直接插入)
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(三)之堆排序
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(二)之希尔排序
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(四)之归并排序
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 树的高度和深度
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 红黑树深入剖析及Java实现
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 线性结构 - Hash
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 线性结构 - 数组、链表、栈、队列
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 逻辑结构 - 树
  • 运维&测试
    • 测试
      • Spock
        • Groovy 简明教程
  • 运维&测试
    • 测试
      • Spock
        • Spock 官方文档
  • 运维&测试
    • 测试
      • Spock
        • Spock单元测试框架介绍以及在美团优选的实践
  • 运维&测试
    • 测试
      • TDD
        • TDD 实践 - FizzFuzzWhizz(一)
  • 运维&测试
    • 测试
      • TDD
        • TDD 实践 - FizzFuzzWhizz(三)
  • 运维&测试
    • 测试
      • TDD
        • TDD 实践 - FizzFuzzWhizz(二)
  • 运维&测试
    • 测试
      • TDD
        • 测试驱动开发(TDD)- 原理篇
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Nacos
          • Nacos 服务注册的原理
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Nacos
          • Nacos 配置中心原理分析
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • 服务调用过程
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • Spring Cloud Bus
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • Spring Cloud Consul
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • Spring Cloud Stream
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Sentinel
          • Sentinel 与 Hystrix 的对比
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Sentinel
          • Sentinel
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • How Hystrix Works
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • Hystrix
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • Hystrix原理与实战
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • Spring Cloud Hystrix基本原理
由 GitBook 提供支持
在本页
  • 1. 注册Mapper接口
  • 1.1 addMapper
  • 1.2 MapperAnnotationBuilder
  • 2. 解析对应的mapper.xml
  • 2.1 重复加载的检查
  • 2.2 加载mapper.xml
  • 3. 解析注解配置的缓存
  • 4. 解析Mapper方法的ResultMap
  • 4.1 解析返回值
  • 4.2 生成ResultMap的名称
  • 4.3 构造ResultMap
  • 5. 构造statement
  • 5.1 解析参数类型
  • 5.2 解析statement方法
  • 5.3 处理statement的配置
  • 5.4 生成MappedStatement

这有帮助吗?

  1. Java FrameWorks
  2. Mybatis

Mybatis(四) - Mapper接口解析

1. 注册Mapper接口

在 MyBatis 全局配置文件的解析时,解析 <mapper> 标签的阶段中,有两处是处理 Mapper 接口的:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 包扫描Mapper接口
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                // 【注意看这里】
                configuration.addMappers(mapperPackage);
            } else {
                // ......
                } else if (resource == null && url == null && mapperClass != null) {
                    // 注册单个Mapper接口
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } // ......
            }
        }
    }
}

这两个位置,是会将 Mapper 接口注册到全局 Configuration 中的!

1.1 addMapper

我们看看 addMapper 都做了什么吧,addMappers 方法的套路肯定是一堆 addMapper 方法嘛,核心还是走 addMapper :

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
}

注意看,它利用了一个 MapperRegistry 去注册和管理 MyBatis 中的所有 Mapper 接口,它的底层一定是一个 Map ,我们重点来看 MapperRegistry 的 addMapper 方法,这里面还是有些道道的:

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 {
            // 记录在Map中,留意value的类型是MapperProxyFactory
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            // 利用MapperAnnotationBuilder解析Mapper接口
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

纵读下来,源码本身的逻辑倒是不难理解啦,我们可以关注一下源码中间部分的几行注释:

It's important that the type is added before the parser is run otherwise the binding may automatically be attempted by the mapper parser. If the type is already known, it won't try.

重要的是,必须在运行 Mapper 解析器之前添加 Mapper 接口类型,否则 Mapper 的解析器可能会自动尝试进行绑定。如果 Mapper 类型是已知的,则不会尝试。

这个地方提到的 “运行 Mapper 解析器之前添加 Mapper 接口类型” 如何理解呢?其实就是上面代码中的 knownMappers.put(type, new MapperProxyFactory<>(type)); 与 parser.parse(); 谁先执行罢了。

下面我们要详细解析的 MapperAnnotationBuilder 了,它是解析 Mapper 接口的核心 API 。

1.2 MapperAnnotationBuilder

在第 10 章中,我们在 bindMapperForNamespace 的小节中就提到了用于解析 Mapper 接口的解析类 MapperAnnotationBuilder ,这里 MyBatis 就是利用的它,去解析 Mapper 接口的。

1.2.1 内部成员

首先我们还是先来看一下内部结构,因为它是专门解析 Mapper 接口中的注解的,所以它不会继承 XMLMapperBuilder ,不过必要的 Configuration 和 MapperBuilderAssistant 倒是都有啦:

public class MapperAnnotationBuilder {
    private final Configuration configuration;
    private final MapperBuilderAssistant assistant;
    private final Class<?> type;
    
    public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
        String resource = type.getName().replace('.', '/') + ".java (best guess)";
        this.assistant = new MapperBuilderAssistant(configuration, resource);
        this.configuration = configuration;
        this.type = type;
    }

不过它的功能跟 XMLConfigBuilder 、XMLMapperBuilder 相似,解析 Mapper 接口都是一个 Builder 负责一个,所以每解析一个 Mapper 接口,就要 new 一个全新的 MapperAnnotationBuilder 。同样的,MapperBuilderAssistant 在 MapperAnnotationBuilder 构造时就已经创建。

1.2.2 核心parse方法

最重要的还是工作的核心方法 parse (貌似这些 Builder 的核心工作方法都叫 parse 哦),这里面还是分为几个步骤的,小册先把注释标注在源码上,重要的环节我们再逐条拆解:

public void parse() {
    String resource = type.toString();
    // 2. 检查接口是否已经加载
    if (!configuration.isResourceLoaded(resource)) {
        // 加载Mapper接口对应的mapper.xml
        loadXmlResource();
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        // 3. 解析注解配置的缓存
        parseCache();
        parseCacheRef();
        // 解析Mapper方法
        for (Method method : type.getMethods()) {
            if (!canHaveStatement(method)) {
                continue;
            }
            // 4. 解析注解配置的ResultMap
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                && method.getAnnotation(ResultMap.class) == null) {
                parseResultMap(method);
            }
            try {
                // 5. 构造statement
                parseStatement(method);
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}

可以发现,整个思路其实跟解析 mapper.xml 大同小异,这里它是根据接口去找对应的 mapper.xml ,解析 mapper.xml 的时候也会去绑定对应的 Mapper 接口,下面的那些动作,跟解析 mapper.xml 的也大差不离。

下面我们还是就重要的环节详细剖析。

2. 解析对应的mapper.xml

首先一上来,它会先把当前要解析的 Mapper 接口全限定名,对应的 mapper.xml 找出来解析。

2.1 重复加载的检查

注意看一下源码,它在加载对应的 mapper.xml 之前会先检查一下该接口是否已经加载:

    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        loadXmlResource();

细心的小伙伴可能会回忆起一个点:之前在第 10 章中提到过,loadXmlResource 方法会检查 mapper.xml 中的 namespace 对应的 Mapper 接口是否已经加载,但请注意这里的检查规则,它是直接检查的 Mapper 接口全限定名哦,下一句的 **loadXmlResource** 方法中才是判断 namespace 的:

对比一下上下两段源码,是不是能想明白一点什么?

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        // 注意上面的判断多了一个namespace的前缀
        // ......
    }
}

为什么非要判断两次呢?想必小伙伴的心里也有答案了吧:

  • 检查 Mapper 接口全限定名,是怕我们真的重复配置 Mapper 接口。

    • 实际情况下可能会出现一种情况,单独配置了某个 Mapper 接口,后来配置 Mapper 扫描的时候又把它扫描进来了,这样就引发 Mapper 接口的重复注册了

  • 检查带着 namespace 前缀的 Mapper 接口全限定名,是为了避免重复加载 mapper.xml的问题。

    • mapper.xml 中配置了相同的 Mapper 接口名,在 MyBatis 初始化的时候先加载了 mapper.xml ,顺便记录了对应的 Mapper 接口,后来包扫描的时候又把这个 Mapper 接口扫描到了,这样也会引发 Mapper 接口的重复注册

理清楚可能引发问题的思路哈,如果实在想不明白,可以动手画画图辅助思考。

2.2 加载mapper.xml

下面的动作就是加载 mapper.xml ,以及解析动作了,我们快速的过一遍源码:

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        String xmlResource = type.getName().replace('.', '/') + ".xml";
        InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
        if (inputStream == null) {
            // Search XML mapper that is not in the module but in the classpath.
            try {
                inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
            } catch (IOException e2) {
                // ignore, resource is not required
            }
        }
        if (inputStream != null) {
            XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, 
                    assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
            xmlParser.parse();
        }
    }
}

可见这个思路非常的清晰:首先它将 Mapper 接口的全限定名中的 . 改为 / ,并拼上 .xml 的后缀,然后尝试加载一下,如果加载不到,拉倒(注释中标注了,mapper.xml 不是必需的),如果能加载到,那就新构造一个 XMLMapperBuilder ,并解析对应的 mapper.xml 。

3. 解析注解配置的缓存

接下来的重点部分是解析缓存配置了,这里着重强调的是二级缓存。

// 3. 解析注解配置的缓存
parseCache();
parseCacheRef();

首先是二级缓存的配置,可以发现它是检查 Mapper 接口有没有标注 @CacheNamespace 注解,如果有的话再处理:

private void parseCache() {
    CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
    if (cacheDomain != null) {
        // ......
    }
}

同样的,二级缓存引用的配置,是检查 Mapper 接口上有没有 @CacheNamespaceRef 注解:

private void parseCacheRef() {
    CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
    if (cacheDomainRef != null) {
        // ......
    }
}

4. 解析Mapper方法的ResultMap

接下来是解析 Mapper 接口中的方法了,这里面有一种比较特殊的配置,就是使用注解组合定义 ResultMap :

    // 解析Mapper方法
    for (Method method : type.getMethods()) {
        if (!canHaveStatement(method)) {
            continue;
        }
        // 4. 解析注解配置的ResultMap
        if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
            && method.getAnnotation(ResultMap.class) == null) {
            parseResultMap(method);
        }

解析 @ResultMap 是基于方法的(毕竟每个方法都可能定义新的结果集映射规则),我们先来快速过一遍源码的总体逻辑(关键注释已标注在源码中):

private String parseResultMap(Method method) {
    // 4.1 解析方法返回值
    Class<?> returnType = getReturnType(method);
    // 获取结果集封装引用的构造器参数定义(类比于<resultMap>中的<constructor>标签)
    Arg[] args = method.getAnnotationsByType(Arg.class);
    // 获取结果集映射规则
    Result[] results = method.getAnnotationsByType(Result.class);
    // 获取类型鉴别器(类比于<discriminator>标签)
    TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
    // 4.2 生成ResultMap的名称
    String resultMapId = generateResultMapName(method);
    // 4.3 构造ResultMap
    applyResultMap(resultMapId, returnType, args, results, typeDiscriminator);
    return resultMapId;
}

可以发现,大体的逻辑,跟解析 mapper.xml 中的 resultMapElements 步骤类似,下面我们还是分步来解析源码中的关键步骤。

4.1 解析返回值

本来吧,对于 Mapper 接口而言,返回值就已经定义在方法上了,应该也不用再解析了,直接取 method.getReturnType() 方法就可以吧!其实这远远没有我们想的那么简单,我们可以先来举几个例子:

  • 返回数组怎么办?

  • 返回 List<T> / Set<T> 怎么办?

  • 返回 Map<K, V> 怎么办?

  • 返回 Optional<T> 怎么办?

对比起 mapper.xml 中的 resultType 可以直接写实体类的全限定类名,是不是发现 Mapper 接口方法的返回值并不能直接拿来用了?因为方法的返回值类型实在是太多了,MyBatis 需要针对这些情况一一处理。

下面我们就这个处理机制,展开研究。

4.1.1 解析实体模型类

getReturnType 方法的第一个 if 分支就是判断返回值是否为普通的实体类:

private Class<?> getReturnType(Method method) {
    Class<?> returnType = method.getReturnType();
    Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type);
    if (resolvedReturnType instanceof Class) {
        returnType = (Class<?>) resolvedReturnType;
        if (returnType.isArray()) {
            returnType = returnType.getComponentType();
        }
        // gcode issue #508
        if (void.class.equals(returnType)) {
            ResultType rt = method.getAnnotation(ResultType.class);
            if (rt != null) {
                returnType = rt.value();
            }
        }

可以看出,只要返回值属于 Class<?> 类型,MyBatis 都会认定是我们自己定义的实体类,不过这里面还有两种特殊情况:实体类数组的话,会取出当中具体的实体类型;如果是返回 void 的话,MyBatis 会使用 @ResultType 注解的类型当做返回值类型。这部分比较简单,也很好理解。

4.1.2 解析单列集合泛型

接下来要解析泛型了,上面我们举的例子中提到过,List 、Set 等集合有泛型,Optional 也是有泛型的,所以这几种情况需要分别处理。

首先我们看到的 else-if 结构中,下面的这段源码是处理单列集合 ( Collection ) 和 Cursor 游标的:(源码中有部分注释)

    } else if (resolvedReturnType instanceof ParameterizedType) {
        // 取出泛型的类型
        ParameterizedType parameterizedType = (ParameterizedType) resolvedReturnType;
        Class<?> rawType = (Class<?>) parameterizedType.getRawType();
        if (Collection.class.isAssignableFrom(rawType) || Cursor.class.isAssignableFrom(rawType)) {
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            // 注意这里只会处理泛型个数为1个的
            if (actualTypeArguments != null && actualTypeArguments.length == 1) {
                Type returnTypeParameter = actualTypeArguments[0];
                // 泛型类型为实体模型类
                if (returnTypeParameter instanceof Class<?>) {
                    returnType = (Class<?>) returnTypeParameter;
                }
                // 套娃泛型
                else if (returnTypeParameter instanceof ParameterizedType) {
                    // (gcode issue #443) actual type can be a also a parameterized type
                    returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
                }
                // 泛型数组
                else if (returnTypeParameter instanceof GenericArrayType) {
                    Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
                    // (gcode issue #525) support List<byte[]>
                    returnType = Array.newInstance(componentType, 0).getClass();
                }
            }

源码看上去挺多,但干的事很明确,就是把泛型中的类型取出来,根据对应的情况进行解析和获取。

注意下面的大 if 结构中,中间有一个套娃泛型,这其实说的是类似这种情况:List<List<T>> (类似于二维数组)(当然小册只是举一个栗子),这种情况最终我们应该获取到的是里面的 T ,而不是外头的 List<T> ;最下面的泛型数组,最常见的就是 byte[] 了,源码中的注释也是说了,这是专门为了支持 byte[] 而添加的额外判断分支。

4.1.3 解析Map集合泛型

对于 Map 来讲,value 列肯定是数据库中查询出来的数据对象,但 key 是啥呢?哎,这个地方是细节吧,key 通常来讲肯定是主键没错了,但 MyBatis 不知道谁是主键呀,所以需要我们告诉它,而告诉它的方法是在定义好的 ResultMap 方法上标注 @MapKey 注解。

下面是一个简单的示例,用这种方式可以将查询结果封装为 Map 。

@MapKey("id")
@ResultType(Department.class)
@Select("select * from tbl_department")
Map<String, Department> findAllUseMap();

了解了返回 Map 的方式,下面我们就可以看源码的处理逻辑了:

        } else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(rawType)) {
            // (gcode issue 504) Do not look into Maps if there is not MapKey annotation
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            // 泛型个数必须为2个
            if (actualTypeArguments != null && actualTypeArguments.length == 2) {
                // 解析第1个,即value类型
                Type returnTypeParameter = actualTypeArguments[1];
                if (returnTypeParameter instanceof Class<?>) {
                    returnType = (Class<?>) returnTypeParameter;
                } else if (returnTypeParameter instanceof ParameterizedType) {
                    // (gcode issue 443) actual type can be a also a parameterized type
                    returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
                }
            }

注意第一行,它必须要同时满足返回值为 Map ,并且方法上有 @MapKey 注解的标注,这样才算一个有效的 ResultMap 定义。解析的逻辑中,它主要是处理 value 的类型,而这个解析类型跟上面解析单列集合的套路基本一致,咱就不多啰嗦了。

4.1.4 解析Optional类型

最后一部分是对 jdk8 中引入的 Optional 类型单独处理:

        } else if (Optional.class.equals(rawType)) {
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            Type returnTypeParameter = actualTypeArguments[0];
            if (returnTypeParameter instanceof Class<?>) {
                returnType = (Class<?>) returnTypeParameter;
            }
        }
    }

    return returnType;
}

这里面的解析依然是非常简单,只不过这里面不可能出现套娃泛型,以及泛型数组的情况了。

最后我们简单总结一下这一段源码解析的思路,无非就是为了获取类似于 mapper.xml 中定义的 parameterType ,只不过因为 Mapper 接口中的返回值类型情况有点多,MyBatis 为了顾及所有可能出现的情况,在底层设计了足够多的解析逻辑罢了。

4.2 生成ResultMap的名称

解析完返回值之后,之后又解析了 @Arg 注解、@Result 注解、@TypeDiscriminator 注解,然后一个比较大的动作是生成 ResultMap 的名称,也就是 resultMapId ,这个 id 类比于 mapper.xml 中的【 namespace + <resultMap> 标签中的 id 】。

下面我们看看它的底层是如何生成 resultMapId 的:

private String generateResultMapName(Method method) {
    Results results = method.getAnnotation(Results.class);
    // 有定义,直接取
    if (results != null && !results.id().isEmpty()) {
        return type.getName() + "." + results.id();
    }
    // 没有定义,自动生成
    StringBuilder suffix = new StringBuilder();
    for (Class<?> c : method.getParameterTypes()) {
        suffix.append("-");
        suffix.append(c.getSimpleName());
    }
    if (suffix.length() < 1) {
        suffix.append("-void");
    }
    return type.getName() + "." + method.getName() + suffix;
}

其实我们之前在演示 @Results 注解的时候就发现了,这个注解可以声明 id 属性,它就相当于 <resultMap> 标签的 id ,那么它最终生成的 resultMapId 就是 【 Mapper 接口的全限定名 + @Results 注解声明的 id 属性】。

但还有没定义的情况,它会利用 OOP 的方法重载特性,直接取方法的参数列表类型并组装起来,以构成 ResultMap 的 id 。举几个例子哈:

  • List<Department> findAll() :findAll-void

  • Department findById(String id) :findById-String

  • List<User> findByUsernameAndPassword(String username, String password) :findByUsernameAndPassword-String-String

还是很容易理解的哈,小伙伴们可以自己找几个前面定义过的比划一下。

4.3 构造ResultMap

上面的准备工作都就绪了,下面就可以构造 ResultMap 对象了。这个构建的 ResultMap 对象,跟第 10 章中解析 mapper.xml 的那个 ResultMap 对象是一样的,与 mapper.xml 中封装构建 ResultMap 不同,Mapper 接口的处理稍有不同:

private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results, TypeDiscriminator discriminator) {
    // 处理resultMapping
    List<ResultMapping> resultMappings = new ArrayList<>();
    applyConstructorArgs(args, returnType, resultMappings);
    applyResults(results, returnType, resultMappings);
    // 处理鉴别器
    Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator);
    // 构造ResultMap
    assistant.addResultMap(resultMapId, returnType, null, disc, resultMappings, null);
    createDiscriminatorResultMaps(resultMapId, returnType, discriminator);
}

简单聊一下这段源码的逻辑。

1、首先是 resultMapping 的处理,还记得之前在 mapper.xml 中什么时机解析的 <result> 标签吗?在解析 mapper.xml 中,它会取出那些 <resultMap> 标签,并逐个解析子标签,这里面就有解析 <id> 、<result> 标签的逻辑。在 Mapper 接口中,它选择在此处处理:

List<ResultMapping> resultMappings = new ArrayList<>();
applyConstructorArgs(args, returnType, resultMappings);
applyResults(results, returnType, resultMappings);

可见处理的思路是一样的,都是取构造器参数,和不同的属性映射关系。

2、鉴别器的处理,mapper.xml 中使用鉴别器,用的是 <case> 标签,对应的 Mapper 接口中用的就是 @Case 注解。

3、构造 ResultMap ,这里没有再用之前 mapper.xml 中那么“做作”的手段(当然人家做作是有原因的),而是直接借助 MapperBuilderAssistant 构建了,至于原因嘛,各位可以想一下,Mapper 接口中的 ResultMap 在一定义时,是不是基本的元素就都有了?不会像 mapper.xml 那样出现一些元素不全、漏写的问题吧!所以在这里大可不必做兜底处理,直接构造即可。

5. 构造statement

最后的部分才是本章的核心(压轴?!),那就是解析我们定义的那些 statement 方法了:

    try {
        // 5. 构造statement
        parseStatement(method);
    } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
    }

这个方法超级复杂,直接一次性贴出源码有点不大合适,小册直接拆分成多个片段解析吧,这样也稍微好接受一点。

5.1 解析参数类型

void parseStatement(Method method) {
    final Class<?> parameterTypeClass = getParameterType(method);
    final LanguageDriver languageDriver = getLanguageDriver(method);
    // ......

一上来第一句代码就是复杂逻辑,它要先把这个 statement 的入参类型提取出来。有关入参的类型,大概有以下几种:

  • List<Department> findAll() → 无入参

  • Department findById(String id) → 单参数

  • List<User> findByUsernameAndPassword(String username, String password) → 多参数

针对这几种情况,MyBatis 会指定不同的入参类型,下面我们来看源码中的实现:

private Class<?> getParameterType(Method method) {
    Class<?> parameterType = null;
    Class<?>[] parameterTypes = method.getParameterTypes();
    for (Class<?> currentParameterType : parameterTypes) {
        if (!RowBounds.class.isAssignableFrom(currentParameterType) && !ResultHandler.class.isAssignableFrom(currentParameterType)) {
            if (parameterType == null) {
                parameterType = currentParameterType;
            } else {
                // issue #135
                parameterType = ParamMap.class;
            }
        }
    }
    return parameterType;
}

通过简单的推演,可以大概判断出上面提到的三种情况,最终获取到的 parameterType 为:

  • 无入参 → null

  • 单参数 → Class<?>

  • 多参数 → ParamMap.class

另外注意一个小细节,它会过滤掉 RowBounds (内存分页用)以及 ResultHandler (自定义处理查询结果)类型的参数,原因也很简单,它们不会参与实际的 SQL 拼接中。

5.2 解析statement方法

接下来的一部分内容比较长,不过都比较容易理解,我们快速的过一遍即可:(关键注释已标注在源码中)

    // ......
    getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
        // 【复杂】构造SQL语句源
        final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), 
                      parameterTypeClass, languageDriver, method);
        final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
        // 解析statement的配置
        final Options options = getAnnotationWrapper(method, false, Options.class)
                                        .map(x -> (Options)x.getAnnotation()).orElse(null);
        // 生成statementId
        final String mappedStatementId = type.getName() + "." + method.getName();

        final KeyGenerator keyGenerator;
        String keyProperty = null;
        String keyColumn = null;
        // 处理KeyGenerator
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
            // first check for SelectKey annotation - that overrides everything else
            SelectKey selectKey = getAnnotationWrapper(method, false, SelectKey.class)
                                          .map(x -> (SelectKey)x.getAnnotation()).orElse(null);
            if (selectKey != null) {
                keyGenerator = handleSelectKeyAnnotation(selectKey, 
                                       mappedStatementId, getParameterType(method), languageDriver);
                keyProperty = selectKey.keyProperty();
            } else if (options == null) {
                keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            } else {
                keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
                keyProperty = options.keyProperty();
                keyColumn = options.keyColumn();
            }
        } else {
            keyGenerator = NoKeyGenerator.INSTANCE;
        }
        // ......

通读下来,是不是感觉思路倒是挺清晰的?虽然前面第 10 章我们没有展开 mapper.xml 的 statement 解析,不过各位也不用担心,小册会在后面的生命周期部分,详细的讲解这两种 statement 的解析和构建的。之所以这里先给各位简单展开,是考虑到注解 statement 的定义解析相对简单一些,理解起来也容易一点。

整段源码没有特别难的部分,最重要的一步是第一行的 buildSqlSource 方法,这个方法很复杂,小册放到后面的生命周期部分再讲解。

5.3 处理statement的配置

下面的一段源码也挺长,不过也都比较容易理解,看上去更像“流水账”式处理:(关键注释已标注在源码中)

        // ......
        // 处理其他的配置
        Integer fetchSize = null;
        Integer timeout = null;
        StatementType statementType = StatementType.PREPARED;
        ResultSetType resultSetType = configuration.getDefaultResultSetType();
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = !isSelect;
        boolean useCache = isSelect;
        if (options != null) {
            if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
                flushCache = true;
            } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
                flushCache = false;
            }
            useCache = options.useCache();
            fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
            timeout = options.timeout() > -1 ? options.timeout() : null;
            statementType = options.statementType();
            if (options.resultSetType() != ResultSetType.DEFAULT) {
                resultSetType = options.resultSetType();
            }
        }

        // 处理resultMapId
        String resultMapId = null;
        if (isSelect) {
            ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
            if (resultMapAnnotation != null) {
                resultMapId = String.join(",", resultMapAnnotation.value());
            } else {
                resultMapId = generateResultMapName(method);
            }
        }
        // ......

上面很长的一段都是处理一些 statement 的配置项,诸如 fetchSize 、useCache 、timeout ,我们就不关心了。下面的一段是处理 resultMapId ,这个东西我们上面刚看到过了,那为什么这里还有处理呢?很简单,上面是处理 ResultMap 的定义,到了具体的 statement 中,我们都是引用那些现成的 resultMap ,所以此处实际上是处理 resultMap 的引用。

5.4 生成MappedStatement

前面的工作都完事了,最后一步就是生成 MappedStatement 对象了:

    // ......
    assistant.addMappedStatement(
        mappedStatementId,
        sqlSource,
        statementType,
        sqlCommandType,
        fetchSize,
        timeout,
        // ParameterMapID
        null,
        parameterTypeClass,
        resultMapId,
        getReturnType(method),
        resultSetType,
        flushCache,
        useCache,
        // TODO gcode issue #577
        false,
        keyGenerator,
        keyProperty,
        keyColumn,
        statementAnnotation.getDatabaseId(),
        languageDriver,
        // ResultSets
        options != null ? nullOrEmpty(options.resultSets()) : null);
    });
}

好家伙,又是这么反人类的超级长的参数列表,好吧我们先不进去看了,太特喵的复杂了,还是到后面的生命周期章节再看吧。

上一页Mybatis下一页Java FrameWorks

最后更新于2年前

这有帮助吗?