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. 原生手动装配
  • 2. 模块装配
  • 2.1 什么是模块
  • 2.2 什么是模块装配
  • 2.3 SpringFramework中的模块装配
  • 2.4 快速体会模块装配
  • 2.5 模块装配的四种方式
  • 3. 条件装配
  • 3.1 Profile
  • 3.2 Conditional

这有帮助吗?

  1. Java FrameWorks
  2. Spring
  3. Spring IOC

IOC - 模块装配和条件装配

1. 原生手动装配

在最原始的 SpringFramework 中,是不支持注解驱动开发的(当时最低支持版本是 1.3 、1.4 ),直到 SpringFramework 2.0 版本,才初步出现了模式注解( @Repository ),到了 SpringFramework 2.5 出现了 @Component 和它的几个派生注解,到了 SpringFramework 3.0 才完全的支持注解驱动开发(当时最低支持版本已经升级到 1.5)。

使用 @Configuration + @Bean 注解组合,或者 @Component + @ComponentScan 注解组合,可以实现编程式 / 声明式的手动装配。这两种方式咱前面已经写过很多了,不再赘述。

不过,咱思考一个问题:如果使用这两种方式,如果要注册的 Bean 很多,要么一个一个的 @Bean 编程式写,要么就得选好包进行组件扫描,而且这种情况还得每个类都标注好 @Component 或者它的衍生注解才行。面对数量很多的 Bean ,这种装配方式很明显会比较麻烦,需要有一个新的解决方案。

2. 模块装配

SpringFramework 3.0 的发布,全面支持了注解驱动开发,随之而来的就是快速方便的模块装配。在正式了解模块装配之前,咱先思考一个问题。

2.1 什么是模块

通常理解下,模块可以理解成一个一个的可以分解、组合、更换的独立的单元,模块与模块之间可能存在一定的依赖,模块的内部通常是高内聚的,一个模块通常都是解决一个独立的问题(如引入事务模块是为了解决数据库操作的最终一致性)。其实按照这个理解来看,我们平时写的一个一个的功能,也可以看成一个个的模块;封装的一个个组件,可以看做是模块。

简单总结下,模块通常具有以下几个特征:

  • 独立的

  • 功能高内聚

  • 可相互依赖

  • 目标明确

2.2 什么是模块装配

明确了模块的定义,下面就可以思考下一个问题了:什么是模块装配?

既然模块是功能单元,那模块装配,就可以理解为把一个模块需要的核心功能组件都装配好,当然如果能有尽可能简便的方式那最好。

2.3 SpringFramework中的模块装配

SpringFramework 中的模块装配,是在 3.1 之后引入大量 **@EnableXXX** 注解,来快速整合激活相对应的模块。

从现在 5.x 的官方文档中已经很难找到 @EnableXXX 的介绍了,小伙伴们可以回溯到 SpringFramework 3.1.0 的官方文档:

在 3.1.5 节中,它有介绍 @EnableXXX 注解的使用,并且它还举了不少例子,这里面不乏有咱可能熟悉的:

  • EnableTransactionManagement :开启注解事务驱动

  • EnableWebMvc :激活 SpringWebMvc

  • EnableAspectJAutoProxy :开启注解 AOP 编程

  • EnableScheduling :开启调度功能(定时任务)

这些内容,咱在后面的学习中都会慢慢遇到的,小伙伴们没有必要现在就搞明白这些注解都是干嘛用的,一步一个脚印学习就好。

下面,咱先来体会一下最简单的模块装配。

2.4 快速体会模块装配

先记住使用模块装配的核心原则:自定义注解 + **@Import** 导入组件。

2.4.1 模块装配场景概述

下面咱构建一个场景:使用代码模拟构建出一个酒馆,酒馆里得有吧台,得有调酒师,得有服务员,还得有老板。这里面具体的设计咱不过多深入,小伙伴自己练习时可以自由发挥。

在这个场景中,ApplicationContext 看作一个酒馆,酒馆里的吧台、调酒师、服务员、老板,这些元素统统看作一个一个的组件。咱用代码模拟实现的最终目的,是可以通过一个注解,同时把这些元素都填充到酒馆中。

目的明确了,下面就开始动手吧。一开始咱先实现最简单的装配方式。

2.4.2 声明自定义注解

既然是酒馆,那咱仿照着 SpringFramework 的写法,咱就来一个 **@EnableTavern** 吧!

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EnableTavern {
    
}

注意注解上面要标注三个元注解,代表它在运行时起效,并且只能标注在类上。

还没完事,模块装配需要一个最核心的注解是 **@Import** ,它要标注在 @EnableTavern 上。不过这个 @Import 中需要传入 value 值,点开看一眼它的源码吧:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 */
	Class<?>[] value();
}

看,文档注释已经写得非常明白了:它可以导入配置类、**ImportSelector** 的实现类,**ImportBeanDefinitionRegistrar** 的实现类,或者普通类。咱这里先来快速上手,所以咱先选择使用普通类导入。

2.4.3 声明老板类

既然先导入普通类,那咱就来整一个老板的类吧,毕竟酒馆必须有老板经营才是呀!

public class Boss {

}

没了,这点代码就够了,连 @Component 注解都不用标注。

然后!咱在上面 @EnableTavern 的 @Import 注解中,填入 Boss 的类:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(Boss.class)
public @interface EnableTavern {
    
}

这样就代表,如果标注了 **@EnableTavern** 注解,就会触发 **@Import** 的效果,向容器中导入一个 **Boss** 类型的 Bean 。

2.4.4 创建配置类

注解驱动,自然少不了配置类。咱声明一个 TavernConfiguration 的配置类,并在类上标注 @Configuration 和 @EnableTavern 注解:

@Configuration
@EnableTavern
public class TavernConfiguration {
    
}

配置类中什么都不用写,只要标注好注解即可。


这样我们就完成了最简单的模块装配。

这个时候可能有小伙伴开始不耐烦了:我去原本我可以用 @Configuration + @Bean 就能完事的,你非得给我整这么一堆,这不是徒增功耗吗?别着急,往上翻一翻 @Import 可以传入的东西,是不是发现普通类是最简单的呀?下面咱就来学习剩下几种更复杂的方式。

2.5 模块装配的四种方式

2.5.1 导入普通类

上面的方式就是导入普通类。

2.5.2 导入配置类

如果需要直接导入一些现有的配置类,使用 @Import 也可以直接加载进来。下面咱来把调酒师搞定。

2.5.2.1 声明调酒师类

调酒师的模型,咱加一个 **name** 的属性吧,暗示着咱要搞不止一个调酒师咯:

public class Bartender {
    
    private String name;
    
    public Bartender(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

2.5.2.2 注册调酒师的对象

如果要注册多个相同类型的 Bean ,现在咱能想到的办法就是通过配置类了。下面咱编写一个 BartenderConfiguration :

@Configuration
public class BartenderConfiguration {
    
    @Bean
    public Bartender zhangxiaosan() {
        return new Bartender("张小三");
    }
    
    @Bean
    public Bartender zhangdasan() {
        return new Bartender("张大三");
    }
    
}

注意哦,如果小伙伴用 IDEA 开发的话,此时这个类会报黄,提示这个配置类还没有被用到过,事实上也确实是这样,咱在驱动 IOC 容器初始化时,用的是只传入一个配置类的方式,所以它肯定不会用到。那想让它起作用,只需要在 @EnableTavern 的 @Import 中把这个配置类加上即可:

@Import({Boss.class, BartenderConfiguration.class})
public @interface EnableTavern {
    
}

注意这里有一个小细节,有小伙伴在学习的时候,启动类里或者配置类上用了包扫描,恰好把这个类扫描到了,导致即使没有 @Import 这个 BartenderConfiguration ,Bartender 调酒师也被注册进 IOC 容器了。这里一定要细心哈,包扫描本身就会扫描配置类,并且让其生效的。如果既想用包扫描,又不想扫到这个类,很简单,把这些配置类拿到别的包里,让包扫描找不到它就好啦。

2.5.3 导入ImportSelector

借助 IDE 打开 ImportSelector ,会发现它是一个接口,它的功能可以从文档注释中读到一些信息:

它是一个接口,它的实现类可以根据指定的筛选标准(通常是一个或者多个注解)来决定导入哪些配置类。

文档注释中想表达的是可以导入配置类,但其实 ImportSelector 也可以导入普通类。下面咱先演示如何使用。

2.5.3.1 声明吧台类

吧台的模型类咱就不搞花里胡哨了,最简单的类模型即可:

public class Bar {
    
}

2.5.3.2 声明注册吧台的配置类

咱为了说明 ImportSelector 不止可以导入配置类,也可以导入普通类,所以这里咱也造一个配置类,来演示两种类型皆可的效果。

@Configuration
public class BarConfiguration {
    
    @Bean
    public Bar bbbar() {
        return new Bar();
    }
}

2.5.3.3 编写ImportSelector的实现类

咱编写一个 BarImportSelector ,来实现 ImportSelector 接口,实现 selectImports 方法:

public class BarImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[0];
    }
}

注意,selectImports 方法的返回值是一个 String 类型的数组,它这样设计的目的是什么呢?咱来看看 selectImports 方法的文档注释:

根据导入的 @Configuration 类的 AnnotationMetadata 选择并返回要导入的类的类名。

哦,合着它要的是一组类名呀,自然肯定是全限定类名咯(没有全限定类名没办法定位具体的类)。那既然这样,咱就在这里面把上面的 Bar 和 BarConfiguration 的类名写进去:

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {Bar.class.getName(), BarConfiguration.class.getName()};
    }

最后,把 @EnableTavern 的 @Import 中把这个 BarImportSelector 导入进去即可。

@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class})
public @interface EnableTavern {
    
}

2.5.4 导入ImportBeanDefinitionRegistrar

如果说 ImportSelector 更像声明式导入的话,那 ImportBeanDefinitionRegistrar 就可以解释为编程式向 IOC 容器中导入 Bean 。不过由于它导入的实际是 BeanDefinition ( Bean 的定义信息),这部分咱还没有接触到,就先不展开大篇幅解释了(如果要解释,那可真的是大篇幅的)。咱先对 ImportBeanDefinitionRegistrar 有一个快速的使用入门即可,后面在讲到 IOC 高级和原理部分,会回过头来详细解析 ImportBeanDefinitionRegistrar 的使用和原理。

2.5.4.1 声明服务员类

离最后的酒馆只剩服务员了:

public class Waiter {
    
}

2.5.4.2 编写ImportBeanDefinitionRegistrar的实现类

咱编写一个 WaiterRegistrar ,实现 ImportBeanDefinitionRegistrar 接口:

public class WaiterRegistrar implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("waiter", new RootBeanDefinition(Waiter.class));
    }
}

这里面的写法小伙伴们先不要过度纠结,跟着写就完事了。简单解释下,这个 registerBeanDefinition 方法传入的两个参数,第一个参数是 Bean 的名称(id),第二个参数中传入的 RootBeanDefinition 要指定 Bean 的字节码( **.class** )。

最后,把 WaiterRegistrar 标注在 @EnableTavern 的 @Import 中:

@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class, WaiterRegistrar.class})
public @interface EnableTavern {
    
}

到这里,@Import 的四种导入的方式也就全部过了一遍,模块装配说白了就是这四种方式的综合使用。

学完这几种方式后,可能小伙伴对模块装配的概念和重要性不是很能感知到,没有关系,后面咱学到 AOP 、事务等章节时,会以这些模块的激活,了解一下模块装配在 SpringFramework 内部的体现。

3. 条件装配

还是拿上一章的酒馆为例。如果这套代码模拟的环境放到一片荒野,那这个时候可能吧台还在,老板还在,但是调酒师肯定就不干活了(荒郊野外哪来那些闲情雅致的人去喝酒呢),所以这个时候调酒师就不应该注册到 IOC 容器了。这种情况下,如果只是模块装配,那就没办法搞定了:只要配置类中声明了 @Bean 注解的方法,那这个方法的返回值就一定会被注册到 IOC 容器成为一个 Bean 。

所以,有没有办法解决这个问题呢?当然是有(不然咱这一章讲个啥呢),先来学习第一种方式:Profile 。

3.1 Profile

SpringFramework 3.1 中就已经引入 Profile 的概念了,可它是什么意思呢?咱先了解一下。

3.1.1 什么是Profile

@Profile 注解可以标注一些组件,当应用上下文的一个或多个指定配置文件处于活动状态时,这些组件允许被注册。

配置文件是一个命名的逻辑组,可以通过 ConfigurableEnvironment.setActiveProfiles 以编程方式激活,也可以通过将 spring.profiles.active 属性设置为 JVM 系统属性,环境变量或 web.xml 中用于 Web 应用的 ServletContext 参数来声明性地激活,还可以通过 @ActiveProfiles 注解在集成测试中声明性地激活配置文件。

简单理解下这段文档注释的意思:@Profile 注解可以标注在组件上,当一个配置属性(并不是文件)激活时,它才会起作用,而激活这个属性的方式有很多种(启动参数、环境变量、web.xml 配置等)。

如果小伙伴看完这段话开始有点感觉了,那说明你可能已经知道它的作用了。说白了,profile 提供了一种可以理解成“基于环境的配置”:根据当前项目的运行时环境不同,可以动态的注册当前运行环境匹配的组件。

下面咱就上一章的场景,为酒馆添加外置的环境因素。

3.1.2 @Profile的使用

刚在上面说了,荒郊野外下,调酒师率先不干了,跑路了,此时调酒师就不会在荒郊野外的环境下存在,只会在城市存在。用代码来表达,就是在注册调酒师的配置类上标注 @Profile :

@Configuration
@Profile("city")
public class BartenderConfiguration {
    @Bean
    public Bartender zhangxiaosan() {
        return new Bartender("张小三");
    }
    
    @Bean
    public Bartender zhangdasan() {
        return new Bartender("张大三");
    }
}

3.1.2.2 编程式设置运行时环境

如果现在直接运行 TavernProfileApplication 的 main 方法,控制台中不会打印 zhangxiaosan 和 zhangdasan :

tavernConfiguration
com.linkedbear.spring.configuration.b_profile.component.Boss
com.linkedbear.spring.configuration.b_profile.component.Bar
com.linkedbear.spring.configuration.b_profile.config.BarConfiguration
bbbar
waiter

默认情况下,ApplicationContext 中的 profile 为 “default”,那上面 @Profile("city") 不匹配,BartenderConfiguration 不会生效,那这两个调酒师也不会被注册到 IOC 容器中。要想让调酒师注册进 IOC 容器,就需要给 ApplicationContext 中设置一下:

public static void main(String[] args) throws Exception {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
    // 给ApplicationContext的环境设置正在激活的profile
    ctx.getEnvironment().setActiveProfiles("city");
    Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}

重新运行 main 方法,发现控制台还是只打印上面那些,两个调酒师还是没有被注册到 IOC 容器中。

这个时候可能有小伙伴要一脸问号了:我去你这不是逗我玩吗?你告诉我用 setActiveProfiles 激活,我好不容易写上,结果不好使???你在骗我吗?

不要着急嘛,这都是节目效果而已(狗头保命)。在生气之余,我希望小伙伴们能停下来思考一下:既然这样写不好使,但我又告诉你这么写,那是不是哪里出了问题呢?结合前面 15 章,咱对 ApplicationContext 的认识,是不是突然意识到了点什么?如果你还记得有个 **refresh** 方法的话,那这个地方就可以大胆猜测了:**是不是在 new AnnotationConfigApplicationContext 的时候,如果传入了配置类,它内部就自动初始化完成了,那些 Bean 也就都创建好了?**如果小伙伴能意识到这一点,说明对前面 ApplicationContext 的学习足够的认真了!(不记得的小伙伴可以回头看 15 章的 2.2.3 章节)

那应该怎么写才行呢?既然在构造方法中传入配置类就自动初始化完成了,那我不传呢?

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

诶?这也不报错啊!那我先这样 new 一个空的呗?然后再设置 profile 是不是就好使了呢?赶紧来试试:

public static void main(String[] args) throws Exception {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.getEnvironment().setActiveProfiles("city");
    ctx.register(TavernConfiguration.class);
    ctx.refresh();
    Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}

这样子一写,再重新运行 main 方法,果然控制台就打印 zhangxiaosan 和 zhangdasan 了!

tavernConfiguration
com.linkedbear.spring.configuration.b_profile.component.Boss
com.linkedbear.spring.configuration.b_profile.config.BartenderConfiguration
zhangxiaosan
zhangdasan
com.linkedbear.spring.configuration.b_profile.component.Bar
com.linkedbear.spring.configuration.b_profile.config.BarConfiguration
bbbar
waiter

3.1.2.3 声明式设置运行时环境

上面编程式配置虽然可以用了,但仔细思考一下,这种方式似乎不实用吧!我都把 profile 硬编码在 .java 里了,那要是切换环境,我还得重新编译来,那图个啥呢?所以肯定还有更好的办法。上面的文档注释中也说了,它可以使用的方法很多,下面咱来演示最容易演示的一种:命令行参数配置。

测试命令行参数的环境变量,需要在 IDEA 中配置启动选项:

这样配置好之后,在 main 方法中改回原来的构造方法传入配置类的形式,运行,控制台仍然会打印 zhangxiaosan 和 zhangdasan 。

修改传入的 jvm 参数,将 city 改成 wilderness ,重新运行 main 方法,发现控制台不再打印 zhangxiaosan 和 zhangdasan ,说明使用 jvm 命令行参数也可以控制 profile 。

除了 jvm 命令行参数,通过 web.xml 的方式也可以设置,不过咱还没有学习到集成 web 开发环境,所以这部分先放一放,后续讲 SpringWebMvc 时会提到它的。

3.1.3 @Profile在实际开发的用途

声明一个 DataSourceConfiguration 类,并一次性声明 3 个 DataSource :

@Configuration
public class DataSourceConfiguration {

    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        return null;
    }

    @Bean
    @Profile("test")
    public DataSource testDataSource() {
        return null;
    }

    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
        return null;
    }
}

这样写完之后,通过 @PropertySource 注解 + 外部配置文件,就可以做到只切换 profile 即可切换不同的数据源。

3.1.4 profile控制不到的地方

profile 强大吗?当然很强大,但它还有一些无法控制的地方。下面咱把场景进一步复杂化:

吧台应该是由老板安置好的,如果酒馆中连老板都没有,那吧台也不应该存在。

这种情况下,用 profile 就不好使了:因为 profile 控制的是整个项目的运行环境,无法根据单个 Bean 的因素决定是否装配。也是因为这个问题,出现了第二种条件装配的方式:**@Conditional** 注解。

3.2 Conditional

看这个注解的名,condition ,很明显就是条件的意思啊,这也太直白明了了。按照惯例,咱先对 Conditional 有个清楚的认识。

3.2.1 什么是Conditional

@Conditional 是在 SpringFramework 4.0 版本正式推出的,它可以让 Bean 的装载基于一些指定的条件,换句话说,被标注 @Conditional 注解的 Bean 要注册到 IOC 容器时,必须全部满足 @Conditional 上指定的所有条件才可以。

在 SpringFramework 的官方文档中,并没有花什么篇幅介绍 @Conditional ,而是让咱们直接去看 javadoc ,不过有一说一,javadoc 里基本上把 @Conditional 的作用都描述明白了:

Indicates that a component is only eligible for registration when all specified conditions match.

A condition is any state that can be determined programmatically before the bean definition is due to be registered (see Condition for details).

  • as a meta-annotation, for the purpose of composing custom stereotype annotations

被 @Conditional 注解标注的组件,只有所有指定条件都匹配时,才有资格注册。条件是可以在要注册 BeanDefinition 之前以编程式确定的任何状态。

@Conditional 注解可以通过以下任何一种方式使用:

  • 作为任何直接或间接用 @Component 注解的类的类型级别注解,包括 @Configuration 类

  • 作为元注解,目的是组成自定义注解

  • 作为任何 @Bean 方法上的方法级注解

如果 @Configuration 配置类被 @Conditional 标记,则与该类关联的所有 @Bean 的工厂方法,@Import 注解和 @ComponentScan 注解也将受条件限制。

简单理解下这段文档注释:@Conditional 注解可以指定匹配条件,而被 @Conditional 注解标注的 组件类 / 配置类 / 组件工厂方法 必须满足 @Conditional 中指定的所有条件,才会被创建 / 解析。

下面咱改造上面提到的场景,来体会 @Conditional 条件装配的实际使用。

3.2.2 @Conditional的使用

3.2.2.1 Bar的创建要基于Boss

在 BarConfiguration 的 Bar 注册中,要指定 Bar 的创建需要 Boss 的存在,反映到代码上就是在 bbbar 方法上标注 @Conditional :

    @Bean
    @Conditional(???)
    public Bar bbbar() {
        return new Bar();
    }

发现 @Conditional 注解中需要传入一个 Condition 接口的实现类数组,说明咱还需要编写条件匹配类做匹配依据。那咱就先写一个匹配条件:

3.2.2.2 条件匹配规则类的编写

声明一个 ExistBossCondition 类,表示它用来判断 IOC 容器中是否存在 Boss 的对象:

public class ExistBossCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getBeanFactory().containsBeanDefinition(Boss.class.getName());
    }
}

注意这个地方用 **BeanDefinition** 做判断而不是 Bean ,考虑的是当条件匹配时,可能 Boss 还没被创建,导致条件匹配出现偏差。

然后,把这个 ExistBossCondition 规则类放入 @Conditional 注解中。

3.2.2 通用抽取

思考一个问题:如果一个项目中,有比较多的组件需要依赖另一些不同的组件,如果每个组件都写一个 Condition 条件,那工程量真的太大了。这个时候咱就要想想了:如果能把这个匹配的规则抽取为通用的方式,那岂不是让条件装配变得容易得多?抱着这个想法,咱来试着修改一下现有的代码。

3.2.2.1 抽取传入的beanName

由于上面咱在文档注释中看到了 @Conditional 可以派生,那就来写一个新的注解吧:@ConditionalOnBean ,意为存在指定的 Bean 时匹配:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {

    String[] beanNames() default {};
}

传入的 Condition 类型为自己声明的 OnBeanCondition :

public class OnBeanCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 先获取目标自定义注解ConditionalOnBean上的beanNames属性
        String[] beanNames = (String[]) metadata.getAnnotationAttributes(ConditionalOnBean.class.getName()).get("beanNames");
        // 逐个校验IOC容器中是否包含传入的bean名称
        for (String beanName : beanNames) {
            if (!context.getBeanFactory().containsBeanDefinition(beanName)) {
                return false;
            }
        }
        return true;
    }
}

3.3.3.2 替换上面的原生@Conditional注解

在 BarConfiguration 中,将 bbbar 方法上的 @Conditional(ExistBossCondition.class) 去掉,换用 @ConditionalOnBean 注解:

@Bean
@ConditionalOnBean(beanNames = "com.linkedbear.spring.configuration.c_conditional.component.Boss")
public Bar bbbar() {
    return new Bar();
}

重新运行 main 方法,发现 bbbar 依然没有创建(此时 @EnableTavern 中已经没有导入 Boss 类了),证明自定义注解已经生效。

3.3.3.3 加入类型匹配

上面只能是抽取 beanName ,传整个类的全限定名真的很费劲。如果当前类路径下本来就有这个类,那直接写进去就好呀。我们希望代码最终改造成这个样子:

@Bean
@ConditionalOnBean(Boss.class)
public Bar bbbar() {
    return new Bar();
}

这样子多简洁啊,因为我已经有 Boss 类了,所以直接写进去就好嘛。那下面咱来改造这个效果。

给 @ConditionalOnBean 注解上添加默认的 value 属性,类型为 Class[] ,这样就可以传入类型了:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
    
    Class<?>[] value() default {};

    String[] beanNames() default {};
}

之后,在 OnBeanCondition 中添加 value 的属性解析:

public class OnBeanCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnBean.class.getName());
        // 匹配类型
        Class<?>[] classes = (Class<?>[]) attributes.get("value");
        for (Class<?> clazz : classes) {
            if (!context.getBeanFactory().containsBeanDefinition(clazz.getName())) {
                return false;
            }
        }
        // 匹配beanName
        String[] beanNames = (String[]) attributes.get("beanNames");
        for (String beanName : beanNames) {
            if (!context.getBeanFactory().containsBeanDefinition(beanName)) {
                return false;
            }
        }
        return true;
    }
}

这样,就可以匹配 bean 的名称为默认的全限定名的情况了。

最后多说一句,小伙伴们在自己动手练习这部分内容时不要过于纠结这里面的内容,其实咱写的这个 @ConditionalOnBean 是参考 SpringBoot 中的 @ConditionalOnBean 注解,人家 SpringBoot 官方实现的功能更严密完善,后续你在项目中用到了 SpringBoot ,那这些 @Conditional 派生的注解尽情用就好。

上一页Spring IOC下一页Java FrameWorks

最后更新于2年前

这有帮助吗?

Interface to be implemented by types that determine which class(es) should be imported based on a given selection criteria, usually one or more annotation attributes.

Select and return the names of which class(es) should be imported based on the AnnotationMetadata of the importing class.

SpringFramework 的官方文档中并没有对 Profile 进行过多的描述,而是借助了一篇官网的博客来详细介绍 Profile 的使用: ,咱这里的讲解也会参考这篇博客的内容。另外的,javadoc 中有对 @Profile 注解的介绍,这个介绍可以说是把 Profile 的设计思想介绍的很到位了:

Indicates that a component is eligible for registration when one or more specified profiles are active. A profile is a named logical grouping that may be activated programmatically via ConfigurableEnvironment.setActiveProfiles or declaratively by setting the spring.profiles.active property as a JVM system property, as an environment variable, or as a Servlet context parameter in web.xml for web applications. Profiles may also be activated declaratively in integration tests via the annotation.

3.1.2.1 Bartender添加

img

以数据源为例,在开发环境、测试环境、生产环境中,项目连接的数据库都是不一样的。如果每切换一个环境都要重新改一遍配置文件,那真的是太麻烦了,所以咱就可以采用 的方式来解决。下面咱来模拟演示这种配置。

The annotation may be used in any of the following ways:

as a type-level annotation on any class directly or indirectly annotated with @Component, including classes

as a method-level annotation on any method

If a class is marked with @Conditional, all of the methods, annotations, and annotations associated with that class will be subject to the conditions.

docs.spring.io/spring/docs…
@Configuration
@Configuration
spring.io/blog/2011/0…
@ActiveProfiles
@Profile
@Profile
@Conditional
@Configuration
@Bean
@Configuration
@Bean
@Import
@ComponentScan